├── .husky └── pre-commit ├── README.md ├── apps ├── playground │ ├── src │ │ ├── styles │ │ │ └── globals.css │ │ ├── vite-env.d.ts │ │ ├── index.css │ │ ├── wait.ts │ │ ├── editor-id-generator.ts │ │ ├── key-generator.ts │ │ ├── primitives │ │ │ ├── spinner.tsx │ │ │ ├── icon.tsx │ │ │ ├── group.tsx │ │ │ ├── toolbar.tsx │ │ │ ├── field.text.tsx │ │ │ ├── field.number.tsx │ │ │ ├── toggle-button.tsx │ │ │ ├── utils.ts │ │ │ ├── separator.tsx │ │ │ ├── container.tsx │ │ │ └── field.select.tsx │ │ ├── main.tsx │ │ ├── plugins │ │ │ ├── looks-like-url.ts │ │ │ ├── looks-like-url.test.ts │ │ │ ├── plugin.link.tsx │ │ │ └── read-files.ts │ │ ├── App.tsx │ │ └── toolbar │ │ │ ├── button-tooltip.tsx │ │ │ ├── keyboard-shortcut-preview.tsx │ │ │ └── button.focus.tsx │ ├── biome.json │ ├── tailwind.config.js │ ├── README.md │ ├── tsconfig.json │ ├── .gitignore │ ├── tsconfig.node.json │ ├── index.html │ ├── eslint.config.js │ └── tsconfig.app.json └── docs │ ├── src │ ├── env.d.ts │ ├── assets │ │ ├── houston.webp │ │ ├── emoji-picker.png │ │ ├── portable-text-logo.png │ │ └── logo.svg │ ├── components │ │ ├── editor │ │ │ ├── editor.astro │ │ │ └── defaultSchema.ts │ │ ├── EventTypesList.astro │ │ └── ui │ │ │ ├── textarea.tsx │ │ │ └── separator.tsx │ └── content │ │ ├── config.ts │ │ └── docs │ │ └── reference │ │ ├── toolbar.mdx │ │ ├── keyboard-shortcuts.mdx │ │ └── editor.mdx │ ├── tsconfig.json │ ├── .gitignore │ └── components.json ├── packages ├── racejar │ ├── .gitignore │ ├── src │ │ ├── jest │ │ │ └── index.ts │ │ ├── vitest │ │ │ └── index.ts │ │ ├── playwright │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── create-parameter-type.ts │ │ └── hooks.ts │ ├── tsconfig.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ └── vitest.config.ts ├── editor │ ├── src │ │ ├── test │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ ├── vitest │ │ │ │ ├── _exports │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── step-context.ts │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ ├── util.get-text-block-text.ts │ │ │ ├── util.is-keyed-segment.ts │ │ │ ├── util.is-equal-selection-points.ts │ │ │ ├── util.is-selection-expanded.ts │ │ │ ├── asserters.ts │ │ │ ├── util.is-selection-collapsed.ts │ │ │ ├── util.is-equal-selections.ts │ │ │ ├── util.is-empty-text-block.ts │ │ │ ├── util.reverse-selection.ts │ │ │ ├── util.selection-point.ts │ │ │ ├── util.get-selection-end-point.ts │ │ │ ├── util.get-selection-start-point.ts │ │ │ ├── util.get-block-start-point.ts │ │ │ ├── util.block-offset-to-block-selection-point.ts │ │ │ ├── key-generator.ts │ │ │ └── util.at-the-beginning-of-block.ts │ │ ├── behaviors │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ ├── behavior.config.ts │ │ │ ├── behavior.types.guard.ts │ │ │ └── index.ts │ │ ├── plugins │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── plugin.editor-ref.tsx │ │ │ ├── plugin.internal.change-ref.tsx │ │ │ ├── plugin.internal.slate-editor-ref.tsx │ │ │ ├── plugin.behavior.tsx │ │ │ └── plugin.internal.portable-text-editor-ref.tsx │ │ ├── selectors │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ ├── selector.get-selection.ts │ │ │ ├── selector.get-value.ts │ │ │ ├── selector.is-selection-expanded.ts │ │ │ ├── selector.is-active-style.ts │ │ │ ├── selector.is-active-list-item.ts │ │ │ ├── selector.get-active-annotation-marks.ts │ │ │ ├── selector.get-first-block.ts │ │ │ ├── selector.get-selection-end-point.ts │ │ │ ├── selector.get-selection-start-point.ts │ │ │ ├── selector.get-last-block.ts │ │ │ ├── selector.is-selection-collapsed.ts │ │ │ ├── selector.get-focus-span.ts │ │ │ ├── selector.get-anchor-span.ts │ │ │ ├── selector.get-focus-inline-object.ts │ │ │ ├── selector.get-focus-block-object.ts │ │ │ ├── selector.get-focus-text-block.ts │ │ │ ├── selector.get-selected-spans.ts │ │ │ ├── selector.get-anchor-text-block.ts │ │ │ ├── selector.is-point-after-selection.ts │ │ │ ├── selector.is-point-before-selection.ts │ │ │ ├── selector.get-focus-list-block.ts │ │ │ ├── selector.get-selection-text.ts │ │ │ ├── selector.get-anchor-block.ts │ │ │ ├── selector.get-focus-block.ts │ │ │ ├── selector.is-active-decorator.ts │ │ │ └── selector.get-selection-end-block.ts │ │ ├── internal-utils │ │ │ ├── mime-type.ts │ │ │ ├── schema.ts │ │ │ ├── block-keys.ts │ │ │ ├── string-utils.ts │ │ │ ├── debug.ts │ │ │ ├── split-string.ts │ │ │ ├── move-range-by-operation.ts │ │ │ ├── selection-text.ts │ │ │ ├── text-marks.ts │ │ │ ├── selection-block-keys.ts │ │ │ ├── string-overlap.test.ts │ │ │ ├── create-placeholder-block.ts │ │ │ ├── text-block-key.ts │ │ │ ├── __tests__ │ │ │ │ └── ranges.test.ts │ │ │ ├── compound-client-rect.ts │ │ │ └── collapse-selection.ts │ │ ├── editor │ │ │ ├── editor-schema.ts │ │ │ ├── relay-actor-context.ts │ │ │ ├── editor-actor-context.ts │ │ │ ├── editor-context.tsx │ │ │ ├── slate-plugin.redoing.ts │ │ │ ├── slate-plugin.undoing.ts │ │ │ ├── without-normalizing-conditional.ts │ │ │ ├── withoutPatching.ts │ │ │ ├── with-normalizing-node.ts │ │ │ ├── slate-plugin.without-history.ts │ │ │ ├── withChanges.ts │ │ │ ├── render.drop-indicator.tsx │ │ │ ├── with-performing-behavior-operation.ts │ │ │ ├── render.text.tsx │ │ │ ├── render.default-object.tsx │ │ │ ├── use-editor.ts │ │ │ └── usePortableTextEditor.ts │ │ ├── priority │ │ │ ├── priority.core.ts │ │ │ └── priority.types.ts │ │ ├── types │ │ │ ├── block-offset.ts │ │ │ ├── options.ts │ │ │ ├── sanity-types.ts │ │ │ ├── block-with-optional-key.ts │ │ │ └── slate.ts │ │ ├── operations │ │ │ ├── operation.insert.text.ts │ │ │ ├── operation.move.forward.ts │ │ │ ├── operation.move.backward.ts │ │ │ └── operation.select.ts │ │ ├── converters │ │ │ └── converters.core.ts │ │ └── type-utils.ts │ ├── gherkin-tests │ │ ├── global.d.ts │ │ ├── delete.test.ts │ │ ├── selection.test.ts │ │ ├── insert.text.test.ts │ │ ├── block-objects.test.ts │ │ ├── insert.block.test.ts │ │ ├── insert.blocks.test.ts │ │ ├── insert.child.test.ts │ │ ├── inline-objects.test.ts │ │ ├── lists.test.ts │ │ ├── paste.test.ts │ │ ├── splitting-blocks.test.ts │ │ ├── insert.break.test.ts │ │ ├── selection-adjustment.test.ts │ │ ├── annotations-collaboration.test.ts │ │ └── decorators.test.ts │ ├── tsconfig.dist.json │ ├── .gitignore │ ├── tsconfig.json │ ├── biome.json │ ├── tsconfig.typedoc.json │ ├── gherkin-spec │ │ ├── insert.text.feature │ │ ├── annotations-collaboration.feature │ │ └── removing-blocks.feature │ ├── eslint.config.js │ ├── package.config.ts │ └── tests │ │ └── event.ready.test.tsx ├── block-tools │ ├── src │ │ ├── rules │ │ │ ├── _exports │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── util │ │ │ ├── findBlockType.ts │ │ │ └── randomKey.ts │ │ └── HtmlDeserializer │ │ │ ├── preprocessors │ │ │ ├── xpathResult.ts │ │ │ ├── index.ts │ │ │ └── preprocessor.notion.ts │ │ │ └── rules │ │ │ └── index.ts │ ├── biome.json │ ├── tsconfig.dist.json │ ├── test │ │ ├── tests │ │ │ ├── HtmlDeserializer │ │ │ │ ├── codeBlock │ │ │ │ │ ├── input.html │ │ │ │ │ └── output.json │ │ │ │ ├── simple │ │ │ │ │ ├── input.html │ │ │ │ │ └── index.ts │ │ │ │ ├── whitespace3 │ │ │ │ │ ├── input.html │ │ │ │ │ ├── output.json │ │ │ │ │ └── index.ts │ │ │ │ ├── blockTags │ │ │ │ │ ├── input.html │ │ │ │ │ └── index.ts │ │ │ │ ├── decorators │ │ │ │ │ ├── input.html │ │ │ │ │ └── index.ts │ │ │ │ ├── annotations │ │ │ │ │ ├── input.html │ │ │ │ │ └── index.ts │ │ │ │ ├── customRules │ │ │ │ │ └── input.html │ │ │ │ ├── complex │ │ │ │ │ └── index.ts │ │ │ │ ├── customSchema │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input.html │ │ │ │ ├── fromTheWild1 │ │ │ │ │ └── index.ts │ │ │ │ ├── fromTheWild4 │ │ │ │ │ └── index.ts │ │ │ │ ├── gdocsLists │ │ │ │ │ └── index.ts │ │ │ │ ├── githubIssue │ │ │ │ │ └── index.ts │ │ │ │ ├── whitespace2 │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input.html │ │ │ │ ├── stegaUnicodeCleaner │ │ │ │ │ └── index.ts │ │ │ │ ├── whitespaceInPreTags │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input.html │ │ │ │ ├── types.ts │ │ │ │ ├── gdocs │ │ │ │ │ └── index.ts │ │ │ │ ├── gdocsFirefox │ │ │ │ │ └── index.ts │ │ │ │ ├── gdocsWhitespaceRemove │ │ │ │ │ └── index.ts │ │ │ │ ├── gdocsStrikethroughLink │ │ │ │ │ └── index.ts │ │ │ │ └── gdocsWhitespaceRemoveFirefox │ │ │ │ │ └── index.ts │ │ │ └── util │ │ │ │ └── randomKey.test.ts │ │ ├── test-key-generator.ts │ │ └── html-to-blocks │ │ │ ├── nested-containers.html │ │ │ ├── lists.html │ │ │ ├── from-the-wild-5.html │ │ │ └── lists.test.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── package.config.ts │ └── tsdoc.json ├── plugin-one-line │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.dist.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ └── eslint.config.js ├── plugin-sdk-value │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.dist.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ ├── vitest.config.ts │ └── eslint.config.js ├── plugin-markdown-shortcuts │ ├── src │ │ ├── index.ts │ │ └── global.d.ts │ ├── tsconfig.json │ ├── tsconfig.dist.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ └── eslint.config.js ├── test │ ├── src │ │ ├── index.ts │ │ └── test-key-generator.ts │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── package.config.ts │ └── tsconfig.dist.json ├── plugin-character-pair-decorator │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.dist.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ └── eslint.config.js ├── patches │ ├── src │ │ ├── index.ts │ │ └── arrayInsert.ts │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ ├── package.config.ts │ └── vitest.config.ts ├── plugin-input-rule │ ├── src │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── rule.stock-ticker.feature │ │ └── emoji-picker-rules.feature │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── package.config.ts │ ├── tsconfig.dist.json │ └── eslint.config.js ├── plugin-typography │ ├── src │ │ ├── global.d.ts │ │ ├── index.ts │ │ └── input-rule.multiplication.feature │ ├── assets │ │ ├── typography-demo.gif │ │ └── smart-undo-with-backspace.gif │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── package.config.ts │ ├── tsconfig.dist.json │ └── eslint.config.js ├── plugin-emoji-picker │ ├── src │ │ ├── global.d.ts │ │ ├── index.ts │ │ └── match-emojis.ts │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── package.config.ts │ ├── tsconfig.dist.json │ └── eslint.config.js ├── markdown │ ├── src │ │ ├── from-portable-text │ │ │ └── renderers │ │ │ │ ├── hard-break.ts │ │ │ │ ├── list-item.ts │ │ │ │ └── block-spacing.ts │ │ ├── escape.ts │ │ └── key-generator.ts │ ├── package.config.ts │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ └── vitest.config.ts ├── keyboard-shortcuts │ ├── src │ │ ├── index.ts │ │ └── is-apple.ts │ ├── package.config.ts │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ └── vitest.config.ts ├── schema │ ├── tsconfig.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ ├── README.md │ └── src │ │ └── index.ts ├── sanity-bridge │ ├── tsconfig.json │ ├── package.config.ts │ ├── tsconfig.settings.json │ ├── tsconfig.dist.json │ └── src │ │ ├── index.ts │ │ ├── start-case.ts │ │ └── key-generator.ts └── toolbar │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── package.config.ts │ ├── tsconfig.dist.json │ ├── src │ ├── index.ts │ └── disable-listener.ts │ └── eslint.config.js ├── examples ├── basic │ ├── src │ │ ├── vite-env.d.ts │ │ └── main.tsx │ ├── tsconfig.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.node.json │ ├── tsconfig.app.json │ ├── vite.config.ts │ └── eslint.config.js └── legacy │ ├── src │ ├── vite-env.d.ts │ └── main.tsx │ ├── tsconfig.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── tsconfig.app.json │ └── eslint.config.js ├── .gitignore ├── .changeset ├── twenty-candies-do.md ├── nine-schools-raise.md └── config.json ├── .npmrc ├── .prettierignore ├── .prettierrc.mjs ├── .editorconfig ├── .github └── workflows │ ├── renovate.yml │ ├── release.yml │ ├── check-lint.yml │ ├── check-types.yml │ ├── check-format.yml │ └── check-react-compiler.yml ├── AGENTS.md └── pnpm-workspace.yaml /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/editor/README.md -------------------------------------------------------------------------------- /apps/playground/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/racejar/.gitignore: -------------------------------------------------------------------------------- 1 | test-results 2 | -------------------------------------------------------------------------------- /packages/editor/src/test/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/editor/src/utils/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /apps/playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/basic/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/legacy/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/block-tools/src/rules/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/block-tools/src/rules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flatten-tables' 2 | -------------------------------------------------------------------------------- /packages/editor/src/behaviors/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/editor/src/test/vitest/_exports/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../index' 2 | -------------------------------------------------------------------------------- /packages/plugin-one-line/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin.one-line' 2 | -------------------------------------------------------------------------------- /packages/racejar/src/jest/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jest-gherkin-driver' 2 | -------------------------------------------------------------------------------- /packages/editor/src/test/index.ts: -------------------------------------------------------------------------------- 1 | export * from './gherkin-parameter-types' 2 | -------------------------------------------------------------------------------- /packages/racejar/src/vitest/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vitest-gherkin-driver' 2 | -------------------------------------------------------------------------------- /packages/racejar/src/playwright/index.ts: -------------------------------------------------------------------------------- 1 | export * from './playwright-gherkin-driver' 2 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/src/index.ts: -------------------------------------------------------------------------------- 1 | export {SDKValuePlugin} from './plugin.sdk-value' 2 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/mime-type.ts: -------------------------------------------------------------------------------- 1 | export type MIMEType = `${string}/${string}` 2 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin.markdown-shortcuts' 2 | -------------------------------------------------------------------------------- /packages/test/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './terse-pt' 2 | export * from './test-key-generator' 3 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin.character-pair-decorator' 2 | -------------------------------------------------------------------------------- /apps/docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | __screenshots__ 3 | dist 4 | node_modules 5 | *.tgz 6 | .vercel 7 | .env*.local 8 | .eslintcache 9 | -------------------------------------------------------------------------------- /apps/docs/src/assets/houston.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portabletext/editor/HEAD/apps/docs/src/assets/houston.webp -------------------------------------------------------------------------------- /packages/patches/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './applyPatch' 2 | export * from './patches' 3 | export * from './types' 4 | -------------------------------------------------------------------------------- /.changeset/twenty-candies-do.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@portabletext/editor': patch 3 | --- 4 | 5 | fix: make unique key checks faster 6 | -------------------------------------------------------------------------------- /apps/playground/src/index.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | pre { 4 | @apply text-sm; 5 | @apply overflow-auto; 6 | } 7 | -------------------------------------------------------------------------------- /packages/block-tools/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "//", 3 | "linter": { 4 | "includes": ["!**/*.html"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/docs/src/assets/emoji-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portabletext/editor/HEAD/apps/docs/src/assets/emoji-picker.png -------------------------------------------------------------------------------- /apps/docs/src/assets/portable-text-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portabletext/editor/HEAD/apps/docs/src/assets/portable-text-logo.png -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.feature?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noCheck": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.feature?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-typography/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.feature?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /.changeset/nine-schools-raise.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@portabletext/editor': patch 3 | --- 4 | 5 | fix: avoid redundant nested calls to skip normalization 6 | -------------------------------------------------------------------------------- /packages/block-tools/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noCheck": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/editor/src/test/vitest/index.ts: -------------------------------------------------------------------------------- 1 | export * from './step-context' 2 | export * from './step-definitions' 3 | export * from './test-editor' 4 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.feature?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/racejar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["src", "example", "example-playwright"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/playground/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "//", 3 | "css": { 4 | "parser": { 5 | "tailwindDirectives": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.feature?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /packages/patches/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["**/*.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-match-emojis' 2 | export * from './match-emojis' 3 | export * from './use-emoji-picker' 4 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './input-rule' 2 | export * from './plugin.input-rule' 3 | export * from './text-transform-rule' 4 | -------------------------------------------------------------------------------- /apps/playground/src/wait.ts: -------------------------------------------------------------------------------- 1 | export function wait(delay: number) { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, delay) 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /packages/markdown/src/from-portable-text/renderers/hard-break.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @public 3 | */ 4 | export const DefaultHardBreakRenderer = (): string => ' \n' 5 | -------------------------------------------------------------------------------- /packages/plugin-typography/assets/typography-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portabletext/editor/HEAD/packages/plugin-typography/assets/typography-demo.gif -------------------------------------------------------------------------------- /apps/docs/src/components/editor/editor.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import {PortableTextEditor} from './portable-text-editor' 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keyboard-shortcuts' 2 | export * from './common-shortcuts' 3 | export * from './keyboard-event-definition' 4 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["src"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | {"path": "./tsconfig.app.json"}, 5 | {"path": "./tsconfig.node.json"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | {"path": "./tsconfig.app.json"}, 5 | {"path": "./tsconfig.node.json"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/editor/src/editor/editor-schema.ts: -------------------------------------------------------------------------------- 1 | import type {Schema} from '@portabletext/schema' 2 | 3 | /** 4 | * @public 5 | */ 6 | export type EditorSchema = Schema 7 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/src/is-apple.ts: -------------------------------------------------------------------------------- 1 | export const IS_APPLE = 2 | typeof window !== 'undefined' && 3 | /Mac|iPod|iPhone|iPad/.test(window.navigator.userAgent) 4 | -------------------------------------------------------------------------------- /packages/plugin-typography/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-decorator-guard' 2 | export * from './input-rules.typography' 3 | export * from './plugin.typography' 4 | -------------------------------------------------------------------------------- /packages/editor/src/priority/priority.core.ts: -------------------------------------------------------------------------------- 1 | import {createEditorPriority} from './priority.types' 2 | 3 | export const corePriority = createEditorPriority({name: 'core'}) 4 | -------------------------------------------------------------------------------- /packages/racejar/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compile-feature' 2 | export * from './create-parameter-type' 3 | export * from './step-definitions' 4 | export * from './hooks' 5 | -------------------------------------------------------------------------------- /packages/schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["**/*.ts", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-typography/assets/smart-undo-with-backspace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portabletext/editor/HEAD/packages/plugin-typography/assets/smart-undo-with-backspace.gif -------------------------------------------------------------------------------- /packages/sanity-bridge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["**/*.ts", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/test/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/patches/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/codeBlock/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hei 4 |
const foo = 'bar'
5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/markdown/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/markdown/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-one-line/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/racejar/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/schema/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/test/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/toolbar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/playground/src/editor-id-generator.ts: -------------------------------------------------------------------------------- 1 | export function* editorIdGenerator(): Generator { 2 | let index = 0 3 | while (true) { 4 | yield `${index++}` 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/sanity-bridge/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | dts: 'rolldown', 6 | }) 7 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["src/*.ts", "src/*.tsx"], 4 | "exclude": ["dist", "node_modules", "src/**/*.test.*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-typography/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["src/*.ts", "src/*.tsx"], 4 | "exclude": ["dist", "node_modules", "src/**/*.test.*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "include": ["src/*.ts", "src/*.tsx"], 4 | "exclude": ["dist", "node_modules", "src/**/*.test.*"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/playground/src/key-generator.ts: -------------------------------------------------------------------------------- 1 | export function createKeyGenerator(prefix: string) { 2 | let index = 0 3 | return function keyGenerator(): string { 4 | return `${prefix}${index++}` 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/spinner.tsx: -------------------------------------------------------------------------------- 1 | import {LoaderCircle} from 'lucide-react' 2 | 3 | export function Spinner() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export {BehaviorPlugin} from './plugin.behavior' 2 | export {EditorRefPlugin} from './plugin.editor-ref' 3 | export {EventListenerPlugin} from './plugin.event-listener' 4 | -------------------------------------------------------------------------------- /packages/editor/src/types/block-offset.ts: -------------------------------------------------------------------------------- 1 | import type {BlockPath} from './paths' 2 | 3 | /** 4 | * @beta 5 | */ 6 | export type BlockOffset = { 7 | path: BlockPath 8 | offset: number 9 | } 10 | -------------------------------------------------------------------------------- /packages/markdown/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/racejar/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/toolbar/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["**/*.ts", "**/*.tsx", "src/index.ts"], 4 | "exclude": ["dist", "./node_modules", "src/**/*.test.*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-typography/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/playground/README.md: -------------------------------------------------------------------------------- 1 | # Portable Text Playground 2 | 3 | ```sh 4 | # Install dependencies 5 | pnpm install 6 | # Build ./dist/ 7 | pnpm build 8 | # Run locally on http://localhost:5173 9 | pnpm dev 10 | ``` 11 | -------------------------------------------------------------------------------- /apps/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/src/editor/relay-actor-context.ts: -------------------------------------------------------------------------------- 1 | import {createContext} from 'react' 2 | import type {RelayActor} from './relay-machine' 3 | 4 | export const RelayActorContext = createContext({} as RelayActor) 5 | -------------------------------------------------------------------------------- /packages/editor/src/editor/editor-actor-context.ts: -------------------------------------------------------------------------------- 1 | import {createContext} from 'react' 2 | import type {EditorActor} from './editor-machine' 3 | 4 | export const EditorActorContext = createContext({} as EditorActor) 5 | -------------------------------------------------------------------------------- /packages/plugin-one-line/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import {docsSchema} from '@astrojs/starlight/schema' 2 | import {defineCollection} from 'astro:content' 3 | 4 | export const collections = { 5 | docs: defineCollection({schema: docsSchema()}), 6 | } 7 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/simple/input.html: -------------------------------------------------------------------------------- 1 |

2 | This is markdown with code, strong, and emphasis. And 3 | a link too! 4 |

5 | -------------------------------------------------------------------------------- /packages/sanity-bridge/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "./dist", 6 | "exactOptionalPropertyTypes": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/schema/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "./dist", 6 | "noPropertyAccessFromIndexSignature": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/block-tools/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /logs 3 | *.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | /coverage 7 | 8 | # Dependency directories 9 | /node_modules 10 | 11 | # Compiled code 12 | /lib 13 | /dist 14 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/toolbar/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/editor/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /logs 3 | *.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | /coverage 7 | 8 | # Dependency directories 9 | /node_modules 10 | 11 | # Compiled code 12 | /lib 13 | /dist 14 | .eslintcache 15 | -------------------------------------------------------------------------------- /packages/plugin-one-line/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/schema.ts: -------------------------------------------------------------------------------- 1 | import {Schema} from '@sanity/schema' 2 | 3 | export function compileType(rawType: any) { 4 | return Schema.compile({ 5 | name: 'blockTypeSchema', 6 | types: [rawType], 7 | }).get(rawType.name) 8 | } 9 | -------------------------------------------------------------------------------- /packages/patches/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/plugin-typography/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/schema/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespace3/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | This 5 | is 6 | a 7 | test 8 |

9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/markdown/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/racejar/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/toolbar/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/patches/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | strictOptions: { 6 | noImplicitBrowsersList: 'off', 7 | }, 8 | dts: 'rolldown', 9 | }) 10 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | tsconfig: 'tsconfig.dist.json', 5 | babel: {reactCompiler: true}, 6 | reactCompilerOptions: {target: '19'}, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/sanity-bridge/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/basic/src/main.tsx: -------------------------------------------------------------------------------- 1 | import {StrictMode} from 'react' 2 | import {createRoot} from 'react-dom/client' 3 | import App from './App.tsx' 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ) 10 | -------------------------------------------------------------------------------- /packages/block-tools/test/test-key-generator.ts: -------------------------------------------------------------------------------- 1 | export function createTestKeyGenerator(prefix = 'randomKey') { 2 | let index = 0 3 | 4 | return function keyGenerator() { 5 | const key = `${prefix}${index}` 6 | index++ 7 | return key 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/editor/src/behaviors/behavior.config.ts: -------------------------------------------------------------------------------- 1 | import type {EditorPriority} from '../priority/priority.types' 2 | import type {Behavior} from './behavior.types.behavior' 3 | 4 | export type BehaviorConfig = { 5 | behavior: Behavior 6 | priority: EditorPriority 7 | } 8 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-typography/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=sanity 2 | ; This is needed for prettier to be able to use the plugin specified by the `@sanity/prettier-config` preset 3 | public-hoist-pattern[]=prettier-plugin-packagejson 4 | prefer-workspace-packages = true 5 | link-workspace-packages = deep 6 | -------------------------------------------------------------------------------- /examples/legacy/src/main.tsx: -------------------------------------------------------------------------------- 1 | import {StrictMode} from 'react' 2 | import {createRoot} from 'react-dom/client' 3 | import App from './App.tsx' 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ) 10 | -------------------------------------------------------------------------------- /packages/test/tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "noCheck": true, 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["dist", "node_modules", "src/**/*.test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import {App} from './App.tsx' 4 | 5 | ReactDOM.createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ) 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | dist 3 | lib 4 | CHANGELOG.md 5 | pnpm-lock.yaml 6 | 7 | apps/docs/src/content/docs/api 8 | packages/block-tools/test/**/*.html 9 | .changeset/*.md 10 | packages/markdown/src/example-document.out.md 11 | packages/markdown/src/example-document.advanced.out.md 12 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.get-text-block-text.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextTextBlock} from '@portabletext/schema' 2 | 3 | /** 4 | * @public 5 | */ 6 | export function getTextBlockText(block: PortableTextTextBlock) { 7 | return block.children.map((child) => child.text ?? '').join('') 8 | } 9 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-keyed-segment.ts: -------------------------------------------------------------------------------- 1 | import type {KeyedSegment} from '../types/paths' 2 | 3 | /** 4 | * @public 5 | */ 6 | export function isKeyedSegment(segment: unknown): segment is KeyedSegment { 7 | return typeof segment === 'object' && segment !== null && '_key' in segment 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/src/test-key-generator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @public 3 | */ 4 | export function createTestKeyGenerator(prefix?: string) { 5 | let index = 0 6 | 7 | return function keyGenerator() { 8 | const key = `${prefix ?? ''}k${index}` 9 | index++ 10 | return key 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/block-keys.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextBlock} from '@portabletext/schema' 2 | 3 | export function getBlockKeys(value: Array | undefined) { 4 | if (!value) { 5 | return [] 6 | } 7 | 8 | return value.map((block) => block._key) 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import config from '@sanity/prettier-config' 2 | 3 | export default { 4 | ...config, 5 | printWidth: 80, 6 | plugins: [ 7 | ...config.plugins, 8 | '@ianvs/prettier-plugin-sort-imports', 9 | 'prettier-plugin-astro', 10 | 'prettier-plugin-gherkin', 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /packages/plugin-one-line/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist", 7 | "exactOptionalPropertyTypes": false, 8 | "noUncheckedIndexedAccess": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/blockTags/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Paragraph

4 |

H1

5 |

H2

6 |

H3

7 |

H4

8 |
H5
9 |
H6
10 |
Blockquote
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/block-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.base.json", 4 | "include": ["./src", "./test"], 5 | "compilerOptions": { 6 | "rootDir": ".", 7 | "outDir": "./lib", 8 | 9 | "isolatedModules": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/src/editor/editor-context.tsx: -------------------------------------------------------------------------------- 1 | import type {Editor} from '../editor' 2 | import {createGloballyScopedContext} from '../internal-utils/globally-scoped-context' 3 | 4 | export const EditorContext = createGloballyScopedContext( 5 | '@portabletext/editor/context/editor', 6 | null, 7 | ) 8 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/string-utils.ts: -------------------------------------------------------------------------------- 1 | export function splitString(string: string, searchString: string) { 2 | const index = string.indexOf(searchString) 3 | if (index === -1) { 4 | return [string, ''] 5 | } 6 | return [string.slice(0, index), string.slice(index + searchString.length)] 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist", 7 | "exactOptionalPropertyTypes": false, 8 | "noUncheckedIndexedAccess": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist", 7 | "exactOptionalPropertyTypes": false, 8 | "noUncheckedIndexedAccess": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/docs/src/components/EventTypesList.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import {getExportedArrayFromTsFile} from '@/lib/utils.ts' 3 | 4 | const eventTypes = await getExportedArrayFromTsFile( 5 | Astro.props.filePath, 6 | Astro.props.arrayName, 7 | ) 8 | --- 9 | 10 |
    11 | {eventTypes?.map((type) =>
  • {type}
  • )} 12 |
13 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/tsconfig/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "rootDir": ".", 6 | "outDir": "./dist", 7 | "exactOptionalPropertyTypes": false, 8 | "noUncheckedIndexedAccess": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | charset= utf8 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.snap] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": [".astro/types.d.ts", "**/*"], 4 | "exclude": ["dist"], 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | "jsxImportSource": "react", 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/decorators/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Strong text but 5 | this is also emphasized and 6 | striked 7 | 8 | 9 | Just emphasized 10 |

11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/block-tools/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | environment: 'node', 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/markdown/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | environment: 'node', 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/patches/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | environment: 'node', 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/editor/src/editor/slate-plugin.redoing.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function pluginRedoing(editor: PortableTextSlateEditor, fn: () => void) { 4 | const prev = editor.isRedoing 5 | 6 | editor.isRedoing = true 7 | 8 | fn() 9 | 10 | editor.isRedoing = prev 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/src/editor/slate-plugin.undoing.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function pluginUndoing(editor: PortableTextSlateEditor, fn: () => void) { 4 | const prev = editor.isUndoing 5 | 6 | editor.isUndoing = true 7 | 8 | fn() 9 | 10 | editor.isUndoing = prev 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-selection.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import type {EditorSelection} from '../types/editor' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const getSelection: EditorSelector = (snapshot) => { 8 | return snapshot.context.selection 9 | } 10 | -------------------------------------------------------------------------------- /packages/editor/src/test/vitest/step-context.ts: -------------------------------------------------------------------------------- 1 | import type {Locator} from 'vitest/browser' 2 | import type {Editor} from '../../editor' 3 | 4 | /** 5 | * @internal 6 | */ 7 | export type Context = { 8 | editor: Editor 9 | editorB: Editor 10 | locator: Locator 11 | locatorB: Locator 12 | keyMap?: Map 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | environment: 'node', 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/editor/src/operations/operation.insert.text.ts: -------------------------------------------------------------------------------- 1 | import {Transforms} from 'slate' 2 | import type {OperationImplementation} from './operation.types' 3 | 4 | export const insertTextOperationImplementation: OperationImplementation< 5 | 'insert.text' 6 | > = ({operation}) => { 7 | Transforms.insertText(operation.editor, operation.text) 8 | } 9 | -------------------------------------------------------------------------------- /packages/keyboard-shortcuts/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | environment: 'node', 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/racejar/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: [ 6 | { 7 | plugins: [], 8 | test: { 9 | name: 'unit', 10 | exclude: ['example-playwright'], 11 | }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/editor/src/editor/without-normalizing-conditional.ts: -------------------------------------------------------------------------------- 1 | import {Editor} from 'slate' 2 | 3 | export function withoutNormalizingConditional( 4 | editor: Editor, 5 | predicate: () => boolean, 6 | fn: () => void, 7 | ) { 8 | if (predicate()) { 9 | Editor.withoutNormalizing(editor, fn) 10 | } else { 11 | fn() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/editor/src/editor/withoutPatching.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function withoutPatching( 4 | editor: PortableTextSlateEditor, 5 | fn: () => void, 6 | ): void { 7 | const prev = editor.isPatching 8 | editor.isPatching = false 9 | fn() 10 | editor.isPatching = prev 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/delete.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import deleteFeature from '../gherkin-spec/delete.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: deleteFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-value.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextBlock} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const getValue: EditorSelector> = ( 8 | snapshot, 9 | ) => { 10 | return snapshot.context.value 11 | } 12 | -------------------------------------------------------------------------------- /packages/block-tools/src/util/findBlockType.ts: -------------------------------------------------------------------------------- 1 | import type {BlockSchemaType, SchemaType} from '@sanity/types' 2 | 3 | export function findBlockType(type: SchemaType): type is BlockSchemaType { 4 | if (type.type) { 5 | return findBlockType(type.type) 6 | } 7 | 8 | if (type.name === 'block') { 9 | return true 10 | } 11 | 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/selection.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import selectionFeature from '../gherkin-spec/selection.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: selectionFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/editor/with-normalizing-node.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function withNormalizeNode( 4 | editor: PortableTextSlateEditor, 5 | fn: () => void, 6 | ) { 7 | const prev = editor.isNormalizingNode 8 | editor.isNormalizingNode = true 9 | fn() 10 | editor.isNormalizingNode = prev 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.base.json", 4 | "include": ["./src", "./gherkin-tests", "./tests"], 5 | "compilerOptions": { 6 | "rootDir": ".", 7 | "outDir": "./lib", 8 | 9 | "isolatedModules": false, 10 | "types": ["@vitest/browser-playwright"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/insert.text.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import insertTextFeature from '../gherkin-spec/insert.text.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: insertTextFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/block-objects.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import blockObjectsFeature from '../gherkin-spec/block-objects.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: blockObjectsFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/insert.block.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import insertBlockFeature from '../gherkin-spec/insert.block.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: insertBlockFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/insert.blocks.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import insertBlocksFeature from '../gherkin-spec/insert.blocks.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: insertBlocksFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/insert.child.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import insertChildFeature from '../gherkin-spec/insert.child.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: insertChildFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/legacy/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/inline-objects.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import inlineObjectsFeature from '../gherkin-spec/inline-objects.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: inlineObjectsFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/lists.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import listsFeature from '../gherkin-spec/lists.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: listsFeature, 8 | stepDefinitions: stepDefinitions, 9 | parameterTypes: parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/paste.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import pasteFeature from '../gherkin-spec/paste.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: pasteFeature, 8 | stepDefinitions: stepDefinitions, 9 | parameterTypes: parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/editor/slate-plugin.without-history.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function pluginWithoutHistory( 4 | editor: PortableTextSlateEditor, 5 | fn: () => void, 6 | ): void { 7 | const prev = editor.withHistory 8 | 9 | editor.withHistory = false 10 | 11 | fn() 12 | 13 | editor.withHistory = prev 14 | } 15 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/debug.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug' 2 | 3 | const rootName = 'sanity-pte:' 4 | 5 | export default debug(rootName) 6 | export function debugWithName(name: string): debug.Debugger { 7 | const namespace = `${rootName}${name}` 8 | if (debug && debug.enabled(namespace)) { 9 | return debug(namespace) 10 | } 11 | return debug(rootName) 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/src/types/options.ts: -------------------------------------------------------------------------------- 1 | import type {BaseSyntheticEvent} from 'react' 2 | import type {PortableTextEditor} from '../editor/PortableTextEditor' 3 | 4 | /** 5 | * @beta 6 | */ 7 | export type HotkeyOptions = { 8 | marks?: Record 9 | custom?: Record< 10 | string, 11 | (event: BaseSyntheticEvent, editor: PortableTextEditor) => void 12 | > 13 | } 14 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/annotations/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | I am a strong link 5 | and I am a emphasized link 6 |

7 |

8 | I am a totally different link 9 |

10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespace3/output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_key": "randomKey0", 4 | "_type": "block", 5 | "children": [ 6 | { 7 | "_key": "randomKey1", 8 | "_type": "span", 9 | "marks": [], 10 | "text": "This is a test" 11 | } 12 | ], 13 | "markDefs": [], 14 | "style": "normal" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /packages/editor/src/editor/withChanges.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function withRemoteChanges( 4 | editor: PortableTextSlateEditor, 5 | fn: () => void, 6 | ): void { 7 | const prev = editor.isProcessingRemoteChanges 8 | editor.isProcessingRemoteChanges = true 9 | fn() 10 | editor.isProcessingRemoteChanges = prev 11 | } 12 | -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # generated TS docs files 24 | /src/content/docs/api -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/splitting-blocks.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import splittingBlocksFeature from '../gherkin-spec/splitting-blocks.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: splittingBlocksFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/behaviors/behavior.types.guard.ts: -------------------------------------------------------------------------------- 1 | import type {EditorDom} from '../editor/editor-dom' 2 | import type {EditorSnapshot} from '../editor/editor-snapshot' 3 | 4 | /** 5 | * @beta 6 | */ 7 | export type BehaviorGuard = (payload: { 8 | snapshot: EditorSnapshot 9 | event: TBehaviorEvent 10 | dom: EditorDom 11 | }) => TGuardResponse | false 12 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-equal-selection-points.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelectionPoint} from '../types/editor' 2 | import {isEqualPaths} from './util.is-equal-paths' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isEqualSelectionPoints( 8 | a: EditorSelectionPoint, 9 | b: EditorSelectionPoint, 10 | ) { 11 | return a.offset === b.offset && isEqualPaths(a.path, b.path) 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-selection-expanded.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection} from '../types/editor' 2 | import {isSelectionCollapsed} from './util.is-selection-collapsed' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isSelectionExpanded(selection: EditorSelection) { 8 | if (!selection) { 9 | return false 10 | } 11 | 12 | return !isSelectionCollapsed(selection) 13 | } 14 | -------------------------------------------------------------------------------- /apps/playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .eslintcache 26 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/customRules/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hei 4 |
const foo = 'bar'
5 |
6 |

Quote with emphasis

7 | Emphasis! 8 |
9 |

Test image

10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-selection-expanded.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import {isSelectionCollapsed} from './selector.is-selection-collapsed' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const isSelectionExpanded: EditorSelector = (snapshot) => { 8 | return snapshot.context.selection !== null && !isSelectionCollapsed(snapshot) 9 | } 10 | -------------------------------------------------------------------------------- /apps/playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/insert.break.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import insertBreakFeature from '../gherkin-spec/insert.break.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: insertBreakFeature, 8 | stepDefinitions: stepDefinitions, 9 | parameterTypes: parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/selection-adjustment.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import selectionAdjustmentFeature from '../gherkin-spec/selection-adjustment.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: selectionAdjustmentFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/operations/operation.move.forward.ts: -------------------------------------------------------------------------------- 1 | import {Transforms} from 'slate' 2 | import type {OperationImplementation} from './operation.types' 3 | 4 | export const moveForwardOperationImplementation: OperationImplementation< 5 | 'move.forward' 6 | > = ({operation}) => { 7 | Transforms.move(operation.editor, { 8 | unit: 'character', 9 | distance: operation.distance, 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /apps/playground/src/plugins/looks-like-url.ts: -------------------------------------------------------------------------------- 1 | export function looksLikeUrl(text: string) { 2 | let looksLikeUrl = false 3 | try { 4 | const url = new URL(text) 5 | 6 | if (!sensibleProtocols.includes(url.protocol)) { 7 | return false 8 | } 9 | 10 | looksLikeUrl = true 11 | } catch {} 12 | return looksLikeUrl 13 | } 14 | 15 | const sensibleProtocols = ['http:', 'https:', 'mailto:', 'tel:'] 16 | -------------------------------------------------------------------------------- /packages/editor/src/utils/asserters.ts: -------------------------------------------------------------------------------- 1 | import type {TypedObject} from '@portabletext/schema' 2 | 3 | export function isTypedObject(object: unknown): object is TypedObject { 4 | return isRecord(object) && typeof object._type === 'string' 5 | } 6 | 7 | export function isRecord(value: unknown): value is Record { 8 | return !!value && (typeof value === 'object' || typeof value === 'function') 9 | } 10 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/annotations-collaboration.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import annotationsCollaborationFeature from '../gherkin-spec/annotations-collaboration.feature?raw' 3 | import {parameterTypes} from '../src/test' 4 | import {stepDefinitions} from '../src/test/vitest' 5 | 6 | Feature({ 7 | featureText: annotationsCollaborationFeature, 8 | stepDefinitions, 9 | parameterTypes, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/editor/src/operations/operation.move.backward.ts: -------------------------------------------------------------------------------- 1 | import {Transforms} from 'slate' 2 | import type {OperationImplementation} from './operation.types' 3 | 4 | export const moveBackwardOperationImplementation: OperationImplementation< 5 | 'move.backward' 6 | > = ({operation}) => { 7 | Transforms.move(operation.editor, { 8 | unit: 'character', 9 | distance: operation.distance, 10 | reverse: true, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-active-style.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import {getActiveStyle} from './selector.get-active-style' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isActiveStyle(style: string): EditorSelector { 8 | return (snapshot) => { 9 | const activeStyle = getActiveStyle(snapshot) 10 | 11 | return activeStyle === style 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/legacy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/editor/src/editor/render.drop-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function DropIndicator() { 2 | return ( 3 |
14 | 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "//", 3 | "linter": { 4 | "rules": { 5 | "style": { 6 | "noRestrictedImports": { 7 | "level": "error", 8 | "options": { 9 | "paths": { 10 | "@sanity/types": "Import from 'types/sanity-types' instead to maintain visibility over @sanity/types usage." 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/editor/with-performing-behavior-operation.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSlateEditor} from '../types/slate-editor' 2 | 3 | export function withPerformingBehaviorOperation( 4 | editor: PortableTextSlateEditor, 5 | fn: () => void, 6 | ) { 7 | const prev = editor.isPerformingBehaviorOperation 8 | 9 | editor.isPerformingBehaviorOperation = true 10 | 11 | fn() 12 | 13 | editor.isPerformingBehaviorOperation = prev 14 | } 15 | -------------------------------------------------------------------------------- /packages/block-tools/src/HtmlDeserializer/preprocessors/xpathResult.ts: -------------------------------------------------------------------------------- 1 | // We need this here if run server side 2 | export const _XPathResult = { 3 | ANY_TYPE: 0, 4 | NUMBER_TYPE: 1, 5 | STRING_TYPE: 2, 6 | BOOLEAN_TYPE: 3, 7 | UNORDERED_NODE_ITERATOR_TYPE: 4, 8 | ORDERED_NODE_ITERATOR_TYPE: 5, 9 | UNORDERED_NODE_SNAPSHOT_TYPE: 6, 10 | ORDERED_NODE_SNAPSHOT_TYPE: 7, 11 | ANY_UNORDERED_NODE_TYPE: 8, 12 | FIRST_ORDERED_NODE_TYPE: 9, 13 | } 14 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-active-list-item.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import {getActiveListItem} from './selector.get-active-list-item' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isActiveListItem(listItem: string): EditorSelector { 8 | return (snapshot) => { 9 | const activeListItem = getActiveListItem(snapshot) 10 | 11 | return activeListItem === listItem 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/src/rule.stock-ticker.feature: -------------------------------------------------------------------------------- 1 | Feature: Stock Ticker Rule 2 | 3 | Scenario Outline: Transforms plain text into stock ticker 4 | Given the text 5 | When is inserted 6 | And "{ArrowRight}" is pressed 7 | And "new" is typed 8 | Then the text is 9 | 10 | Examples: 11 | | text | inserted text | new text | 12 | | "" | "{AAPL}" | ",{stock-ticker},new" | 13 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/split-string.ts: -------------------------------------------------------------------------------- 1 | export function splitString(string: string, searchString: string) { 2 | const searchStringIndex = string.indexOf(searchString) 3 | 4 | if (searchStringIndex === -1) { 5 | return [string, ''] as const 6 | } 7 | 8 | const firstPart = string.slice(0, searchStringIndex) 9 | const secondPart = string.slice(searchStringIndex + searchString.length) 10 | 11 | return [firstPart, secondPart] as const 12 | } 13 | -------------------------------------------------------------------------------- /apps/playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import {useActorRef} from '@xstate/react' 2 | import {editorIdGenerator} from './editor-id-generator' 3 | import {Editors} from './editors' 4 | import {playgroundMachine} from './playground-machine' 5 | 6 | export function App() { 7 | const playgroundRef = useActorRef(playgroundMachine, { 8 | input: { 9 | editorIdGenerator: editorIdGenerator(), 10 | }, 11 | }) 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yml: -------------------------------------------------------------------------------- 1 | name: Add changeset to Renovate updates 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, synchronize] 6 | 7 | concurrency: ${{ github.workflow }}-${{ github.ref }} 8 | 9 | permissions: 10 | contents: read # for checkout 11 | 12 | jobs: 13 | call: 14 | uses: portabletext/.github/.github/workflows/changesets-from-conventional-commits.yml@main 15 | if: github.event.pull_request.user.login == 'renovate[bot]' 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-selection-collapsed.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection} from '../types/editor' 2 | import {isEqualPaths} from './util.is-equal-paths' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isSelectionCollapsed(selection: EditorSelection) { 8 | if (!selection) { 9 | return false 10 | } 11 | 12 | return ( 13 | isEqualPaths(selection.anchor.path, selection.focus.path) && 14 | selection.anchor.offset === selection.focus.offset 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/blockTags/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/codeBlock/output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_key": "randomKey0", 4 | "_type": "block", 5 | "children": [ 6 | { 7 | "_key": "randomKey1", 8 | "_type": "span", 9 | "marks": ["em"], 10 | "text": "Hei" 11 | } 12 | ], 13 | "markDefs": [], 14 | "style": "normal" 15 | }, 16 | { 17 | "_key": "randomKey2", 18 | "_type": "code", 19 | "text": "const foo = 'bar'" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/complex/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/customSchema/index.ts: -------------------------------------------------------------------------------- 1 | import customSchema from '../../../fixtures/customSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = customSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/simple/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/sanity-bridge/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createPortableTextMemberSchemaTypes, 3 | type PortableTextMemberSchemaTypes, 4 | } from './portable-text-member-schema-types' 5 | export {portableTextMemberSchemaTypesToSchema} from './portable-text-member-schema-types-to-schema' 6 | export {sanitySchemaToPortableTextSchema} from './sanity-schema-to-portable-text-schema' 7 | export {compileSchemaDefinitionToPortableTextMemberSchemaTypes} from './schema-definition-to-portable-text-member-schema-types' 8 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/annotations/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/decorators/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/fromTheWild1/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/fromTheWild4/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocsLists/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/githubIssue/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespace2/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespace3/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/stegaUnicodeCleaner/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespaceInPreTags/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, commonOptions) 10 | } 11 | 12 | export default testFn 13 | -------------------------------------------------------------------------------- /packages/toolbar/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-toolbar-schema' 2 | export * from './use-annotation-button' 3 | export * from './use-annotation-popover' 4 | export * from './use-block-object-button' 5 | export * from './use-block-object-popover' 6 | export * from './use-decorator-button' 7 | export * from './use-history-buttons' 8 | export * from './use-inline-object-button' 9 | export * from './use-inline-object-popover' 10 | export * from './use-list-button' 11 | export * from './use-style-selector' 12 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/icon.tsx: -------------------------------------------------------------------------------- 1 | import {isValidElement} from 'react' 2 | import {isValidElementType} from 'react-is' 3 | 4 | export function Icon(props: { 5 | icon?: React.ReactNode | React.ComponentType 6 | fallback: string | null 7 | }) { 8 | const IconComponent = props.icon 9 | 10 | return isValidElement(IconComponent) ? ( 11 | IconComponent 12 | ) : isValidElementType(IconComponent) ? ( 13 | 14 | ) : ( 15 | props.fallback 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /apps/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Portable Text Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/editor/src/behaviors/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | effect, 3 | execute, 4 | forward, 5 | raise, 6 | type BehaviorAction, 7 | type BehaviorActionSet, 8 | } from './behavior.types.action' 9 | export {defineBehavior, type Behavior} from './behavior.types.behavior' 10 | export type { 11 | BehaviorEvent, 12 | CustomBehaviorEvent, 13 | InsertPlacement, 14 | NativeBehaviorEvent, 15 | SyntheticBehaviorEvent, 16 | } from './behavior.types.event' 17 | export type {BehaviorGuard} from './behavior.types.guard' 18 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-active-annotation-marks.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSnapshot} from '../editor/editor-snapshot' 2 | import {getMarkState} from './selector.get-mark-state' 3 | 4 | export function getActiveAnnotationsMarks(snapshot: EditorSnapshot) { 5 | const schema = snapshot.context.schema 6 | const markState = getMarkState(snapshot) 7 | 8 | return (markState?.marks ?? []).filter( 9 | (mark) => 10 | !schema.decorators.map((decorator) => decorator.name).includes(mark), 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-first-block.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextBlock} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {BlockPath} from '../types/paths' 4 | 5 | /** 6 | * @public 7 | */ 8 | export const getFirstBlock: EditorSelector< 9 | {node: PortableTextBlock; path: BlockPath} | undefined 10 | > = (snapshot) => { 11 | const node = snapshot.context.value[0] 12 | 13 | return node ? {node, path: [{_key: node._key}]} : undefined 14 | } 15 | -------------------------------------------------------------------------------- /packages/editor/src/editor/render.text.tsx: -------------------------------------------------------------------------------- 1 | import type {Editable} from 'slate-react' 2 | 3 | export type RenderTextProps = Parameters< 4 | NonNullable['renderText']> 5 | >[0] 6 | 7 | export function RenderText(props: RenderTextProps) { 8 | return ( 9 | 15 | {props.children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/group.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | composeRenderProps, 3 | Group as RACGroup, 4 | type GroupProps, 5 | } from 'react-aria-components' 6 | import {tv} from 'tailwind-variants' 7 | 8 | const styles = tv({ 9 | base: 'contents', 10 | }) 11 | 12 | export function Group(props: GroupProps) { 13 | return ( 14 | 17 | styles({...renderProps, className}), 18 | )} 19 | /> 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | permissions: 11 | contents: read # for checkout 12 | 13 | jobs: 14 | release: 15 | uses: portabletext/.github/.github/workflows/changesets.yml@main 16 | permissions: 17 | contents: read # for checkout 18 | id-token: write # to enable use of OIDC for npm provenance 19 | with: 20 | TURBO_TEAM: ${{ vars.TURBO_TEAM }} 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/types.ts: -------------------------------------------------------------------------------- 1 | import type {htmlToBlocks, normalizeBlock} from '../../../src' 2 | import type {TypedObject} from '../../../src/types' 3 | 4 | interface BlockContentFunctions { 5 | normalizeBlock: typeof normalizeBlock 6 | htmlToBlocks: typeof htmlToBlocks 7 | } 8 | 9 | export type BlockTestFn = ( 10 | input: string, 11 | blockTools: BlockContentFunctions, 12 | commonOptions: { 13 | parseHtml: (html: string) => Document 14 | keyGenerator: () => string 15 | }, 16 | ) => TypedObject[] 17 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/plugin.editor-ref.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type {Editor} from '../editor' 3 | import {useEditor} from '../editor/use-editor' 4 | 5 | /** 6 | * @beta 7 | */ 8 | export const EditorRefPlugin = React.forwardRef((_, ref) => { 9 | const editor = useEditor() 10 | 11 | const portableTextEditorRef = React.useRef(editor) 12 | 13 | React.useImperativeHandle(ref, () => portableTextEditorRef.current, []) 14 | 15 | return null 16 | }) 17 | EditorRefPlugin.displayName = 'EditorRefPlugin' 18 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-equal-selections.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection} from '../types/editor' 2 | import {isEqualSelectionPoints} from './util.is-equal-selection-points' 3 | 4 | /** 5 | * @public 6 | */ 7 | export function isEqualSelections(a: EditorSelection, b: EditorSelection) { 8 | if (!a && !b) { 9 | return true 10 | } 11 | 12 | if (!a || !b) { 13 | return false 14 | } 15 | 16 | return ( 17 | isEqualSelectionPoints(a.anchor, b.anchor) && 18 | isEqualSelectionPoints(a.focus, b.focus) 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/editor/src/types/sanity-types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file centralizes all imports from @sanity/types. 3 | * Any types needed from @sanity/types should be re-exported here 4 | * to maintain visibility over the dependency. 5 | */ 6 | 7 | export type { 8 | ArrayDefinition, 9 | ArraySchemaType, 10 | BlockDecoratorDefinition, 11 | BlockListDefinition, 12 | BlockStyleDefinition, 13 | ObjectSchemaType, 14 | // biome-ignore lint/style/noRestrictedImports: This is the designated file for @sanity/types imports 15 | } from '@sanity/types' 16 | -------------------------------------------------------------------------------- /apps/docs/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.mjs", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocs/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, { 10 | ...commonOptions, 11 | unstable_whitespaceOnPasteMode: 'normalize', 12 | }) 13 | } 14 | 15 | export default testFn 16 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-selection-end-point.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import type {EditorSelectionPoint} from '../types/editor' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const getSelectionEndPoint: EditorSelector< 8 | EditorSelectionPoint | undefined 9 | > = (snapshot) => { 10 | if (!snapshot.context.selection) { 11 | return undefined 12 | } 13 | 14 | return snapshot.context.selection.backward 15 | ? snapshot.context.selection.anchor 16 | : snapshot.context.selection.focus 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "rootDir": ".", 6 | "outDir": "./lib", 7 | "isolatedModules": false, 8 | "types": ["node"] 9 | }, 10 | "include": ["./src"], 11 | "exclude": [ 12 | "./gherkin-tests", 13 | "./tests", 14 | "**/*.test.ts", 15 | "**/*.test.tsx", 16 | "**/*.spec.ts", 17 | "**/*.spec.tsx", 18 | "**/__tests__/**", 19 | "**/test/**", 20 | "**/tests/**" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | composeRenderProps, 3 | Toolbar as RACToolbar, 4 | type ToolbarProps, 5 | } from 'react-aria-components' 6 | import {tv} from 'tailwind-variants' 7 | 8 | const styles = tv({ 9 | base: 'flex gap-2 flex-wrap', 10 | }) 11 | 12 | export function Toolbar(props: ToolbarProps) { 13 | return ( 14 | 17 | styles({...renderProps, className}), 18 | )} 19 | /> 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/customSchema/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
A heading that is not allowed
4 |

5 | Strong text but 6 | this is also emphasized and 7 | striked 8 | and subbed 9 | and supped 10 | and inserted 11 | and marked 12 | and deleted 13 | and shrunk 14 | 15 | 16 |

17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-selection-start-point.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import type {EditorSelectionPoint} from '../types/editor' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const getSelectionStartPoint: EditorSelector< 8 | EditorSelectionPoint | undefined 9 | > = (snapshot) => { 10 | if (!snapshot.context.selection) { 11 | return undefined 12 | } 13 | 14 | return snapshot.context.selection.backward 15 | ? snapshot.context.selection.focus 16 | : snapshot.context.selection.anchor 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/src/match-emojis.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The base type representing an emoji match. 3 | * 4 | * @beta 5 | */ 6 | export type BaseEmojiMatch = 7 | | { 8 | type: 'exact' 9 | emoji: string 10 | } 11 | | { 12 | type: 'partial' 13 | emoji: string 14 | } 15 | 16 | /** 17 | * A function that returns an array of emoji matches for a given keyword. 18 | * 19 | * @beta 20 | */ 21 | export type MatchEmojis = 22 | (query: {keyword: string}) => ReadonlyArray 23 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | {"repo": "portabletext/editor"} 6 | ], 7 | "commit": false, 8 | "access": "public", 9 | "baseBranch": "main", 10 | "updateInternalDependencies": "patch", 11 | "ignore": ["docs", "playground", "example-basic", "example-legacy"], 12 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 13 | "updateInternalDependents": "always", 14 | "onlyUpdatePeerDependentsWhenOutOfRange": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocsFirefox/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, { 10 | ...commonOptions, 11 | unstable_whitespaceOnPasteMode: 'normalize', 12 | }) 13 | } 14 | 15 | export default testFn 16 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocsWhitespaceRemove/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, { 10 | ...commonOptions, 11 | unstable_whitespaceOnPasteMode: 'remove', 12 | }) 13 | } 14 | 15 | export default testFn 16 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocsStrikethroughLink/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, { 10 | ...commonOptions, 11 | unstable_whitespaceOnPasteMode: 'normalize', 12 | }) 13 | } 14 | 15 | export default testFn 16 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/gdocsWhitespaceRemoveFirefox/index.ts: -------------------------------------------------------------------------------- 1 | import defaultSchema from '../../../fixtures/defaultSchema' 2 | import type {BlockTestFn} from '../types' 3 | 4 | const blockContentType = defaultSchema 5 | .get('blogPost') 6 | .fields.find((field: any) => field.name === 'body').type 7 | 8 | const testFn: BlockTestFn = (html, blockTools, commonOptions) => { 9 | return blockTools.htmlToBlocks(html, blockContentType, { 10 | ...commonOptions, 11 | unstable_whitespaceOnPasteMode: 'remove', 12 | }) 13 | } 14 | 15 | export default testFn 16 | -------------------------------------------------------------------------------- /packages/editor/gherkin-tests/decorators.test.ts: -------------------------------------------------------------------------------- 1 | import {Feature} from 'racejar/vitest' 2 | import decoratorsOverlappingFeature from '../gherkin-spec/decorators-overlapping.feature?raw' 3 | import decoratorsFeature from '../gherkin-spec/decorators.feature?raw' 4 | import {parameterTypes} from '../src/test' 5 | import {stepDefinitions} from '../src/test/vitest' 6 | 7 | Feature({ 8 | featureText: decoratorsFeature, 9 | stepDefinitions, 10 | parameterTypes, 11 | }) 12 | 13 | Feature({ 14 | featureText: decoratorsOverlappingFeature, 15 | stepDefinitions, 16 | parameterTypes, 17 | }) 18 | -------------------------------------------------------------------------------- /apps/docs/src/components/editor/defaultSchema.ts: -------------------------------------------------------------------------------- 1 | import {defineSchema} from '@portabletext/editor' 2 | 3 | export const defaultSchema = defineSchema({ 4 | decorators: [{name: 'strong'}, {name: 'em'}, {name: 'underline'}], 5 | annotations: [{name: 'link', fields: [{name: 'href', type: 'string'}]}], 6 | styles: [ 7 | {name: 'normal'}, 8 | {name: 'h1'}, 9 | {name: 'h2'}, 10 | {name: 'h3'}, 11 | {name: 'blockquote'}, 12 | ], 13 | lists: [{name: 'bullet'}, {name: 'number'}], 14 | inlineObjects: [{name: 'stock-ticker'}], 15 | blockObjects: [{name: 'image'}], 16 | }) 17 | -------------------------------------------------------------------------------- /packages/editor/gherkin-spec/insert.text.feature: -------------------------------------------------------------------------------- 1 | Feature: Insert text 2 | 3 | Background: 4 | Given one editor 5 | 6 | Scenario Outline: Inserting text on expanded selection 7 | Given the text 8 | When is selected 9 | And "new" is typed 10 | Then the text is 11 | 12 | Examples: 13 | | text | selection | new text | 14 | | "foo\|bar" | "oo" | "fnew\|bar" | 15 | | "foo\|bar" | "b" | "foo\|newar" | 16 | | "foo\|bar" | "ooba" | "fnewr" | 17 | | "foo\|bar" | "foobar" | "new" | 18 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/move-range-by-operation.ts: -------------------------------------------------------------------------------- 1 | import {Point, type Operation, type Range} from 'slate' 2 | 3 | export function moveRangeByOperation( 4 | range: Range, 5 | operation: Operation, 6 | ): Range | null { 7 | const anchor = Point.transform(range.anchor, operation) 8 | const focus = Point.transform(range.focus, operation) 9 | 10 | if (anchor === null || focus === null) { 11 | return null 12 | } 13 | 14 | if (Point.equals(anchor, range.anchor) && Point.equals(focus, range.focus)) { 15 | return range 16 | } 17 | 18 | return {anchor, focus} 19 | } 20 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/selection-text.ts: -------------------------------------------------------------------------------- 1 | import {getTersePt} from '@portabletext/test' 2 | import type {EditorContext} from '../editor/editor-snapshot' 3 | import {sliceBlocks} from '../utils/util.slice-blocks' 4 | 5 | export function getSelectionText( 6 | context: Pick< 7 | EditorContext, 8 | 'keyGenerator' | 'schema' | 'value' | 'selection' 9 | >, 10 | ) { 11 | if (!context.selection) { 12 | return [] 13 | } 14 | 15 | const slice = sliceBlocks({ 16 | context, 17 | blocks: context.value, 18 | }) 19 | 20 | return getTersePt({schema: context.schema, value: slice}) 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/plugin.internal.change-ref.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import {usePortableTextEditor} from '../editor/usePortableTextEditor' 3 | import type {EditorChange} from '../types/editor' 4 | 5 | export function InternalChange$Plugin(props: { 6 | onChange: (change: EditorChange) => void 7 | }) { 8 | const change$ = usePortableTextEditor().change$ 9 | 10 | useEffect(() => { 11 | const subscription = change$.subscribe(props.onChange) 12 | 13 | return () => { 14 | subscription.unsubscribe() 15 | } 16 | }, [change$, props.onChange]) 17 | 18 | return null 19 | } 20 | -------------------------------------------------------------------------------- /apps/playground/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/block-tools/src/HtmlDeserializer/preprocessors/index.ts: -------------------------------------------------------------------------------- 1 | import {preprocessWordOnline} from '../word-online/preprocessor.word-online' 2 | import {preprocessGDocs} from './preprocessor.gdocs' 3 | import {preprocessHTML} from './preprocessor.html' 4 | import {preprocessNotion} from './preprocessor.notion' 5 | import {preprocessWhitespace} from './preprocessor.whitespace' 6 | import {preprocessWord} from './preprocessor.word' 7 | 8 | export const preprocessors = [ 9 | preprocessWhitespace, 10 | preprocessNotion, 11 | preprocessWord, 12 | preprocessWordOnline, 13 | preprocessGDocs, 14 | preprocessHTML, 15 | ] 16 | -------------------------------------------------------------------------------- /packages/block-tools/test/html-to-blocks/nested-containers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Header

5 |
6 | I'm a quote. 7 |
8 | And I'm a quote within a quote. 9 |
10 | And I'm a quote within a quote within a quote. 11 |
12 | And I'm a quote within a quote within a quote within a quote. 13 |

I am a stupid paragraph within with a stupid link

14 |
15 |
16 |
17 |
18 |

Footer

19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/block-tools/test/tests/HtmlDeserializer/whitespaceInPreTags/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
 4 |       # 1. Clone the repository:
 5 |       git clone ...
 6 |       # 2. Navigate to the client directory:
 7 |       cd ...
 8 |       # 3. Create a new Python virtual environment:
 9 |       python3 -m venv ...
10 |     
11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/plugin.internal.slate-editor-ref.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {useSlateStatic} from 'slate-react' 3 | import type {PortableTextSlateEditor} from '../types/slate-editor' 4 | 5 | export const InternalSlateEditorRefPlugin = 6 | React.forwardRef((_, ref) => { 7 | const slateEditor = useSlateStatic() 8 | 9 | const slateEditorRef = React.useRef(slateEditor) 10 | 11 | React.useImperativeHandle(ref, () => slateEditorRef.current, []) 12 | 13 | return null 14 | }) 15 | InternalSlateEditorRefPlugin.displayName = 'InternalSlateEditorRefPlugin' 16 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/field.text.tsx: -------------------------------------------------------------------------------- 1 | import {TextField as RACTextField} from 'react-aria-components' 2 | import {Input, Label} from './field' 3 | 4 | export function TextField(props: { 5 | name: string 6 | label?: string 7 | autoFocus?: boolean 8 | defaultValue?: string 9 | }) { 10 | return ( 11 | 17 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/priority/priority.types.ts: -------------------------------------------------------------------------------- 1 | import {defaultKeyGenerator} from '../utils/key-generator' 2 | 3 | export type EditorPriority = { 4 | id: string 5 | name?: string 6 | reference?: { 7 | priority: EditorPriority 8 | importance: 'higher' | 'lower' 9 | } 10 | } 11 | 12 | export function createEditorPriority(config?: { 13 | name?: string 14 | reference?: { 15 | priority: EditorPriority 16 | importance: 'higher' | 'lower' 17 | } 18 | }): EditorPriority { 19 | return { 20 | id: defaultKeyGenerator(), 21 | name: config?.name, 22 | reference: config?.reference, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/sanity-bridge/src/start-case.ts: -------------------------------------------------------------------------------- 1 | export function startCase(str: string): string { 2 | return ( 3 | str 4 | // Insert space before uppercase letters in camelCase (e.g., 'fooBar' -> 'foo Bar') 5 | .replace(/([a-z])([A-Z])/g, '$1 $2') 6 | // Replace underscores and dashes with spaces 7 | .replace(/[_-]+/g, ' ') 8 | // Trim and split on whitespace 9 | .trim() 10 | .split(/\s+/) 11 | .filter(Boolean) 12 | // Capitalize first letter of each word, preserve rest 13 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 14 | .join(' ') 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/field.number.tsx: -------------------------------------------------------------------------------- 1 | import {NumberField as RACNumberField} from 'react-aria-components' 2 | import {Input, Label} from './field' 3 | 4 | export function NumberField(props: { 5 | name: string 6 | label?: string 7 | autoFocus?: boolean 8 | defaultValue?: number 9 | }) { 10 | return ( 11 | 17 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-last-block.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextBlock} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {BlockPath} from '../types/paths' 4 | 5 | /** 6 | * @public 7 | */ 8 | export const getLastBlock: EditorSelector< 9 | {node: PortableTextBlock; path: BlockPath} | undefined 10 | > = (snapshot) => { 11 | const node = snapshot.context.value[snapshot.context.value.length - 1] 12 | ? snapshot.context.value[snapshot.context.value.length - 1] 13 | : undefined 14 | 15 | return node ? {node, path: [{_key: node._key}]} : undefined 16 | } 17 | -------------------------------------------------------------------------------- /packages/editor/gherkin-spec/annotations-collaboration.feature: -------------------------------------------------------------------------------- 1 | Feature: Annotations Collaboration 2 | 3 | Background: 4 | Given two editors 5 | And a global keymap 6 | 7 | Scenario: Editor B inserts text after Editor A's half-deleted annotation 8 | When "foo" is typed 9 | And "foo" is selected 10 | And "comment" "c1" is toggled 11 | And the caret is put after "foo" 12 | And "{Backspace}" is pressed 13 | And Editor B is focused 14 | And the caret is put after "fo" in Editor B 15 | And "a" is typed in Editor B 16 | Then the text is "fo,a" 17 | And "fo" has marks "c1" 18 | And "a" has no marks 19 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-selection-collapsed.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import {isEqualPaths} from '../utils/util.is-equal-paths' 3 | 4 | /** 5 | * @public 6 | */ 7 | export const isSelectionCollapsed: EditorSelector = (snapshot) => { 8 | if (!snapshot.context.selection) { 9 | return false 10 | } 11 | 12 | return ( 13 | isEqualPaths( 14 | snapshot.context.selection.anchor.path, 15 | snapshot.context.selection.focus.path, 16 | ) && 17 | snapshot.context.selection.anchor.offset === 18 | snapshot.context.selection.focus.offset 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin-one-line/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', '*.test.tsx']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-sdk-value/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', '*.test.tsx']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /apps/docs/src/content/docs/reference/toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Toolbar API Overview 3 | description: Toolbar reference 4 | prev: false 5 | next: false 6 | --- 7 | 8 | import {CardGrid, LinkCard} from '@astrojs/starlight/components' 9 | import {PackageManagers} from 'starlight-package-managers' 10 | 11 | The Toolbar API offers assorted hooks and types for building your own toolbars. 12 | 13 | 14 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/toggle-button.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | composeRenderProps, 3 | ToggleButton as RACToggleButton, 4 | type ToggleButtonProps, 5 | } from 'react-aria-components' 6 | import {button} from './button' 7 | 8 | export function ToggleButton(props: ToggleButtonProps & {size?: 'sm'}) { 9 | return ( 10 | 13 | button({ 14 | ...renderProps, 15 | size: props.size, 16 | className, 17 | variant: 'secondary', 18 | }), 19 | )} 20 | /> 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /packages/toolbar/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', 'lib', '**/__tests__/**']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-emoji-picker/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', 'lib', '**/*.test.*']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', 'lib', '**/*.test.*']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-markdown-shortcuts/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', '**/*.test.tsx']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-typography/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', 'lib', '**/*.test.*']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/plugin-character-pair-decorator/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['dist', '*.test.tsx']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: {'react-hooks/exhaustive-deps': 'error'}, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /packages/editor/src/editor/render.default-object.tsx: -------------------------------------------------------------------------------- 1 | import type {PortableTextChild, PortableTextObject} from '@portabletext/schema' 2 | 3 | export function RenderDefaultBlockObject(props: { 4 | blockObject: PortableTextObject 5 | }) { 6 | return ( 7 |
8 | [{props.blockObject._type}: {props.blockObject._key}] 9 |
10 | ) 11 | } 12 | 13 | export function RenderDefaultInlineObject(props: { 14 | inlineObject: PortableTextObject | PortableTextChild 15 | }) { 16 | return ( 17 | 18 | [{props.inlineObject._type}: {props.inlineObject._key}] 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/text-marks.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, isTextBlock} from '@portabletext/schema' 2 | import type {EditorContext} from '../editor/editor-snapshot' 3 | 4 | export function getTextMarks( 5 | context: Pick, 6 | text: string, 7 | ) { 8 | let marks: Array = [] 9 | 10 | for (const block of context.value) { 11 | if (isTextBlock(context, block)) { 12 | for (const child of block.children) { 13 | if (isSpan(context, child) && child.text === text) { 14 | marks = child.marks ?? [] 15 | break 16 | } 17 | } 18 | } 19 | } 20 | 21 | return marks 22 | } 23 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-focus-span.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, type PortableTextSpan} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {ChildPath} from '../types/paths' 4 | import {getFocusChild} from './selector.get-focus-child' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getFocusSpan: EditorSelector< 10 | {node: PortableTextSpan; path: ChildPath} | undefined 11 | > = (snapshot) => { 12 | const focusChild = getFocusChild(snapshot) 13 | 14 | return focusChild && isSpan(snapshot.context, focusChild.node) 15 | ? {node: focusChild.node, path: focusChild.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/eslint.config.js: -------------------------------------------------------------------------------- 1 | import reactHooks from 'eslint-plugin-react-hooks' 2 | import {globalIgnores} from 'eslint/config' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config([ 6 | globalIgnores(['coverage', 'dist', 'lib', '**/__tests__/**']), 7 | reactHooks.configs.flat.recommended, 8 | { 9 | files: ['src/**/*.{cjs,mjs,js,jsx,ts,tsx}'], 10 | languageOptions: { 11 | parser: tseslint.parser, 12 | parserOptions: { 13 | projectService: true, 14 | tsconfigRootDir: import.meta.dirname, 15 | }, 16 | }, 17 | rules: { 18 | 'react-hooks/exhaustive-deps': 'error', 19 | }, 20 | }, 21 | ]) 22 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-anchor-span.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, type PortableTextSpan} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {ChildPath} from '../types/paths' 4 | import {getAnchorChild} from './selector.get-anchor-child' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getAnchorSpan: EditorSelector< 10 | {node: PortableTextSpan; path: ChildPath} | undefined 11 | > = (snapshot) => { 12 | const anchorChild = getAnchorChild(snapshot) 13 | 14 | return anchorChild && isSpan(snapshot.context, anchorChild.node) 15 | ? {node: anchorChild.node, path: anchorChild.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/selection-block-keys.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection} from '../types/editor' 2 | import {getBlockKeyFromSelectionPoint} from '../utils/util.selection-point' 3 | 4 | export function getSelectionBlockKeys(selection: EditorSelection) { 5 | if (!selection) { 6 | return undefined 7 | } 8 | 9 | const anchorBlockKey = getBlockKeyFromSelectionPoint(selection.anchor) 10 | const focusBlockKey = getBlockKeyFromSelectionPoint(selection.focus) 11 | 12 | if (anchorBlockKey === undefined || focusBlockKey === undefined) { 13 | return undefined 14 | } 15 | 16 | return { 17 | anchor: anchorBlockKey, 18 | focus: focusBlockKey, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-focus-inline-object.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, type PortableTextObject} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {ChildPath} from '../types/paths' 4 | import {getFocusChild} from './selector.get-focus-child' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getFocusInlineObject: EditorSelector< 10 | {node: PortableTextObject; path: ChildPath} | undefined 11 | > = (snapshot) => { 12 | const focusChild = getFocusChild(snapshot) 13 | 14 | return focusChild && !isSpan(snapshot.context, focusChild.node) 15 | ? {node: focusChild.node, path: focusChild.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.is-empty-text-block.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, isTextBlock, type PortableTextBlock} from '@portabletext/schema' 2 | import type {EditorContext} from '../editor/editor-snapshot' 3 | import {getTextBlockText} from './util.get-text-block-text' 4 | 5 | /** 6 | * @public 7 | */ 8 | export function isEmptyTextBlock( 9 | context: Pick, 10 | block: PortableTextBlock | unknown, 11 | ) { 12 | if (!isTextBlock(context, block)) { 13 | return false 14 | } 15 | 16 | const onlyText = block.children.every((child) => isSpan(context, child)) 17 | const blockText = getTextBlockText(block) 18 | 19 | return onlyText && blockText === '' 20 | } 21 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/utils.ts: -------------------------------------------------------------------------------- 1 | import {composeRenderProps} from 'react-aria-components' 2 | import {twMerge} from 'tailwind-merge' 3 | import {tv} from 'tailwind-variants' 4 | 5 | export const focusRing = tv({ 6 | base: 'outline outline-blue-600 forced-colors:outline-[Highlight] outline-offset-2', 7 | variants: { 8 | isFocusVisible: { 9 | false: 'outline-0', 10 | true: 'outline-2', 11 | }, 12 | }, 13 | }) 14 | 15 | export function composeTailwindRenderProps( 16 | className: string | ((v: T) => string) | undefined, 17 | tw: string, 18 | ): string | ((v: T) => string) { 19 | return composeRenderProps(className, (className) => twMerge(tw, className)) 20 | } 21 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/string-overlap.test.ts: -------------------------------------------------------------------------------- 1 | import {expect, test} from 'vitest' 2 | import {stringOverlap} from './string-overlap' 3 | 4 | test(stringOverlap.name, () => { 5 | expect(stringOverlap('', 'foobar')).toBe('') 6 | expect(stringOverlap('foo ', 'o bar b')).toBe('o ') 7 | expect(stringOverlap('foo', 'o')).toBe('o') 8 | expect(stringOverlap('foo bar baz', 'o')).toBe('o') 9 | expect(stringOverlap('bar', 'o bar b')).toBe('bar') 10 | expect(stringOverlap(' baz', 'o bar b')).toBe(' b') 11 | expect(stringOverlap('fofofo', 'fo')).toBe('fo') 12 | expect(stringOverlap('fofofo', 'fof')).toBe('fof') 13 | expect(stringOverlap('fofofo', 'fofofof')).toBe('fofofo') 14 | }) 15 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.reverse-selection.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection} from '../types/editor' 2 | 3 | /** 4 | * @public 5 | */ 6 | export function reverseSelection< 7 | TEditorSelection extends NonNullable | null, 8 | >(selection: TEditorSelection): TEditorSelection { 9 | if (!selection) { 10 | return selection 11 | } 12 | 13 | if (selection.backward) { 14 | return { 15 | anchor: selection.focus, 16 | focus: selection.anchor, 17 | backward: false, 18 | } as TEditorSelection 19 | } 20 | 21 | return { 22 | anchor: selection.focus, 23 | focus: selection.anchor, 24 | backward: true, 25 | } as TEditorSelection 26 | } 27 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.selection-point.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelectionPoint} from '../types/editor' 2 | import {isKeyedSegment} from './util.is-keyed-segment' 3 | 4 | export function getBlockKeyFromSelectionPoint(point: EditorSelectionPoint) { 5 | const blockPathSegment = point.path.at(0) 6 | 7 | if (isKeyedSegment(blockPathSegment)) { 8 | return blockPathSegment._key 9 | } 10 | 11 | return undefined 12 | } 13 | 14 | export function getChildKeyFromSelectionPoint(point: EditorSelectionPoint) { 15 | const childPathSegment = point.path.at(2) 16 | 17 | if (isKeyedSegment(childPathSegment)) { 18 | return childPathSegment._key 19 | } 20 | 21 | return undefined 22 | } 23 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/plugin.behavior.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import type {Behavior} from '../behaviors/behavior.types.behavior' 3 | import {useEditor} from '../editor/use-editor' 4 | 5 | /** 6 | * @beta 7 | */ 8 | export function BehaviorPlugin(props: {behaviors: Array}) { 9 | const editor = useEditor() 10 | 11 | useEffect(() => { 12 | const unregisterBehaviors = props.behaviors.map((behavior) => 13 | editor.registerBehavior({behavior}), 14 | ) 15 | 16 | return () => { 17 | unregisterBehaviors.forEach((unregister) => { 18 | unregister() 19 | }) 20 | } 21 | }, [editor, props.behaviors]) 22 | 23 | return null 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-focus-block-object.ts: -------------------------------------------------------------------------------- 1 | import {isTextBlock, type PortableTextObject} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {BlockPath} from '../types/paths' 4 | import {getFocusBlock} from './selector.get-focus-block' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getFocusBlockObject: EditorSelector< 10 | {node: PortableTextObject; path: BlockPath} | undefined 11 | > = (snapshot) => { 12 | const focusBlock = getFocusBlock(snapshot) 13 | 14 | return focusBlock && !isTextBlock(snapshot.context, focusBlock.node) 15 | ? {node: focusBlock.node, path: focusBlock.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-focus-text-block.ts: -------------------------------------------------------------------------------- 1 | import {isTextBlock, type PortableTextTextBlock} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {BlockPath} from '../types/paths' 4 | import {getFocusBlock} from './selector.get-focus-block' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getFocusTextBlock: EditorSelector< 10 | {node: PortableTextTextBlock; path: BlockPath} | undefined 11 | > = (snapshot) => { 12 | const focusBlock = getFocusBlock(snapshot) 13 | 14 | return focusBlock && isTextBlock(snapshot.context, focusBlock.node) 15 | ? {node: focusBlock.node, path: focusBlock.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-selected-spans.ts: -------------------------------------------------------------------------------- 1 | import {isSpan, type PortableTextSpan} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {ChildPath} from '../types/paths' 4 | import {getSelectedChildren} from './selector.get-selected-children' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getSelectedSpans: EditorSelector< 10 | Array<{ 11 | node: PortableTextSpan 12 | path: ChildPath 13 | }> 14 | > = (snapshot) => { 15 | if (!snapshot.context.selection) { 16 | return [] 17 | } 18 | 19 | return getSelectedChildren({ 20 | filter: (child) => isSpan(snapshot.context, child), 21 | })(snapshot) 22 | } 23 | -------------------------------------------------------------------------------- /packages/markdown/src/escape.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Escapes special characters in image alt texts and link texts. 3 | */ 4 | export function escapeImageAndLinkText(text: string): string { 5 | return text.replace(/([[\]\\])/g, '\\$1') 6 | } 7 | 8 | /** 9 | * Unescapes special characters in image alt texts and link texts. 10 | */ 11 | export function unescapeImageAndLinkText(text: string): string { 12 | return text.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g, '$1') 13 | } 14 | 15 | /** 16 | * Escapes special characters in image/link titles (the part inside quotes). 17 | */ 18 | export function escapeImageAndLinkTitle(text: string): string { 19 | return text.replace(/([\\"])/g, '\\$1') 20 | } 21 | -------------------------------------------------------------------------------- /packages/editor/src/internal-utils/create-placeholder-block.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextSpan} from '@portabletext/schema' 2 | import type {EditorContext} from '../editor/editor-snapshot' 3 | 4 | export function createPlaceholderBlock( 5 | context: Pick, 6 | ) { 7 | return { 8 | _type: context.schema.block.name, 9 | _key: context.keyGenerator(), 10 | style: context.schema.styles[0].name ?? 'normal', 11 | markDefs: [], 12 | children: [ 13 | { 14 | _type: context.schema.span.name, 15 | _key: context.keyGenerator(), 16 | text: '', 17 | marks: [], 18 | } as PortableTextSpan, 19 | ], 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.get-anchor-text-block.ts: -------------------------------------------------------------------------------- 1 | import {isTextBlock, type PortableTextTextBlock} from '@portabletext/schema' 2 | import type {EditorSelector} from '../editor/editor-selector' 3 | import type {BlockPath} from '../types/paths' 4 | import {getAnchorBlock} from './selector.get-anchor-block' 5 | 6 | /** 7 | * @public 8 | */ 9 | export const getAnchorTextBlock: EditorSelector< 10 | {node: PortableTextTextBlock; path: BlockPath} | undefined 11 | > = (snapshot) => { 12 | const anchorBlock = getAnchorBlock(snapshot) 13 | 14 | return anchorBlock && isTextBlock(snapshot.context, anchorBlock.node) 15 | ? {node: anchorBlock.node, path: anchorBlock.path} 16 | : undefined 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/plugins/plugin.internal.portable-text-editor-ref.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type {PortableTextEditor} from '../editor/PortableTextEditor' 3 | import {usePortableTextEditor} from '../editor/usePortableTextEditor' 4 | 5 | export const InternalPortableTextEditorRefPlugin = 6 | React.forwardRef((_, ref) => { 7 | const portableTextEditor = usePortableTextEditor() 8 | 9 | const portableTextEditorRef = React.useRef(portableTextEditor) 10 | 11 | React.useImperativeHandle(ref, () => portableTextEditorRef.current, []) 12 | 13 | return null 14 | }) 15 | InternalPortableTextEditorRefPlugin.displayName = 16 | 'InternalPortableTextEditorRefPlugin' 17 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.get-selection-end-point.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection, EditorSelectionPoint} from '../types/editor' 2 | 3 | /** 4 | * @public 5 | */ 6 | export function getSelectionEndPoint< 7 | TEditorSelection extends NonNullable | null, 8 | TEditorSelectionPoint extends EditorSelectionPoint | null = 9 | TEditorSelection extends NonNullable 10 | ? EditorSelectionPoint 11 | : null, 12 | >(selection: TEditorSelection): TEditorSelectionPoint { 13 | if (!selection) { 14 | return null as TEditorSelectionPoint 15 | } 16 | 17 | return ( 18 | selection.backward ? selection.anchor : selection.focus 19 | ) as TEditorSelectionPoint 20 | } 21 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "Bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /examples/legacy/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "Bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/utils/util.get-selection-start-point.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelection, EditorSelectionPoint} from '../types/editor' 2 | 3 | /** 4 | * @public 5 | */ 6 | export function getSelectionStartPoint< 7 | TEditorSelection extends NonNullable | null, 8 | TEditorSelectionPoint extends EditorSelectionPoint | null = 9 | TEditorSelection extends NonNullable 10 | ? EditorSelectionPoint 11 | : null, 12 | >(selection: TEditorSelection): TEditorSelectionPoint { 13 | if (!selection) { 14 | return null as TEditorSelectionPoint 15 | } 16 | 17 | return ( 18 | selection.backward ? selection.focus : selection.anchor 19 | ) as TEditorSelectionPoint 20 | } 21 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Bash commands 2 | 3 | - pnpm build - Build package 4 | - pnpm check:types - Typecheck package 5 | - pnpm test:unit - Run package unit tests 6 | - pnpm test:browser - Run package browser tests 7 | - pnpm test:browser:chromium - Run specific browser tests 8 | - pnpm check:lint - Lint package 9 | 10 | # Code style 11 | 12 | - Avoid code comments unless they explain **why** a piece of code is needed 13 | - Comments for an if statement go inside the if statement, not above it 14 | - Place helper functions below main functions 15 | - Only use type casting as a last resort 16 | - Use backticks when referencing code in comments and other text 17 | - Don't use one-character variable names 18 | - Use full, easily-understood variable names 19 | -------------------------------------------------------------------------------- /apps/playground/src/primitives/separator.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Separator as RACSeparator, 3 | type SeparatorProps, 4 | } from 'react-aria-components' 5 | import {tv} from 'tailwind-variants' 6 | 7 | const styles = tv({ 8 | base: 'bg-gray-300 text-gray-300', 9 | variants: { 10 | orientation: { 11 | horizontal: 'h-px w-full', 12 | vertical: 'w-px', 13 | }, 14 | }, 15 | defaultVariants: { 16 | orientation: 'horizontal', 17 | }, 18 | }) 19 | 20 | export function Separator(props: SeparatorProps) { 21 | return ( 22 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | define: { 5 | __DEV__: false, 6 | }, 7 | dist: 'lib', 8 | extract: { 9 | customTags: [ 10 | { 11 | name: 'group', 12 | allowMultiple: true, 13 | syntaxKind: 'block', 14 | }, 15 | ], 16 | rules: { 17 | // Disable rules for now 18 | 'ae-incompatible-release-tags': 'off', 19 | }, 20 | }, 21 | tsconfig: 'tsconfig.dist.json', 22 | strictOptions: { 23 | noImplicitBrowsersList: 'off', 24 | noImplicitSideEffects: 'error', 25 | }, 26 | babel: {reactCompiler: true}, 27 | reactCompilerOptions: {target: '19'}, 28 | dts: 'rolldown', 29 | }) 30 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-point-after-selection.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import type {EditorSelectionPoint} from '../types/editor' 3 | import {comparePoints} from '../utils/util.compare-points' 4 | import {getSelectionEndPoint} from '../utils/util.get-selection-end-point' 5 | 6 | /** 7 | * @public 8 | */ 9 | export function isPointAfterSelection( 10 | point: EditorSelectionPoint, 11 | ): EditorSelector { 12 | return (snapshot) => { 13 | if (!snapshot.context.selection) { 14 | return false 15 | } 16 | 17 | const endPoint = getSelectionEndPoint(snapshot.context.selection) 18 | 19 | return comparePoints(snapshot, point, endPoint) === 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/racejar/src/create-parameter-type.ts: -------------------------------------------------------------------------------- 1 | import {ParameterType, type RegExps} from '@cucumber/cucumber-expressions' 2 | 3 | /** 4 | * @public 5 | */ 6 | export type ParameterTypeConfig = { 7 | readonly name: string 8 | matcher: RegExps 9 | type?: (...args: unknown[]) => TType 10 | transform?: (...match: string[]) => TType 11 | } 12 | 13 | export type {ParameterType} 14 | 15 | /** 16 | * @public 17 | */ 18 | export function createParameterType( 19 | config: ParameterTypeConfig, 20 | ): ParameterType { 21 | return new ParameterType( 22 | config.name, 23 | config.matcher, 24 | config.type ?? String, 25 | config.transform, 26 | false, 27 | true, 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /packages/block-tools/test/html-to-blocks/lists.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 |
  • 5 | 1 a 6 |
      7 |
    • 8 | 2 a 9 |
        10 |
      • 3 a
      • 11 |
      • 3 b
      • 12 |
      13 |
    • 14 |
    • 15 | 2 b 16 |
    • 17 |
    18 |
  • 19 |
  • 20 | 1 b 21 |
  • 22 |
  • 23 | Link 24 |
  • 25 |
  • 26 |

    p in li.

    27 |

    block children are still processed.

    28 |
  • 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/editor/src/converters/converters.core.ts: -------------------------------------------------------------------------------- 1 | import type {PortableTextMemberSchemaTypes} from '../types/editor' 2 | import {converterJson} from './converter.json' 3 | import {converterPortableText} from './converter.portable-text' 4 | import {createConverterTextHtml} from './converter.text-html' 5 | import {converterTextMarkdown} from './converter.text-markdown' 6 | import {createConverterTextPlain} from './converter.text-plain' 7 | 8 | export function createCoreConverters( 9 | legacySchema: PortableTextMemberSchemaTypes, 10 | ) { 11 | return [ 12 | converterJson, 13 | converterPortableText, 14 | converterTextMarkdown, 15 | createConverterTextHtml(legacySchema), 16 | createConverterTextPlain(legacySchema), 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/editor/src/editor/use-editor.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {EditorContext} from './editor-context' 3 | 4 | /** 5 | * @public 6 | * Get the current editor context from the `EditorProvider`. 7 | * Must be used inside the `EditorProvider` component. 8 | * @returns The current editor object. 9 | * @example 10 | * ```tsx 11 | * import { useEditor } from '@portabletext/editor' 12 | * 13 | * function MyComponent() { 14 | * const editor = useEditor() 15 | * } 16 | * ``` 17 | * @group Hooks 18 | */ 19 | export function useEditor() { 20 | const editor = React.useContext(EditorContext) 21 | 22 | if (!editor) { 23 | throw new Error('No Editor set. Use EditorProvider to set one.') 24 | } 25 | 26 | return editor 27 | } 28 | -------------------------------------------------------------------------------- /packages/block-tools/test/html-to-blocks/from-the-wild-5.html: -------------------------------------------------------------------------------- 1 |

TRANSFORM is currently supporting over 2 | 45 projects across 11 countries, 3 | which have reached more than a million people so far.

4 | 5 |

Unlocking the power of markets

6 | Drinking water 7 |

8 | TRANSFORM brings together the public and private sectors to address the world's most pressing development challenges

9 | Visit the TRANSFORM website to discover more
10 | -------------------------------------------------------------------------------- /packages/editor/src/selectors/selector.is-point-before-selection.ts: -------------------------------------------------------------------------------- 1 | import type {EditorSelector} from '../editor/editor-selector' 2 | import type {EditorSelectionPoint} from '../types/editor' 3 | import {comparePoints} from '../utils/util.compare-points' 4 | import {getSelectionStartPoint} from '../utils/util.get-selection-start-point' 5 | 6 | /** 7 | * @public 8 | */ 9 | export function isPointBeforeSelection( 10 | point: EditorSelectionPoint, 11 | ): EditorSelector { 12 | return (snapshot) => { 13 | if (!snapshot.context.selection) { 14 | return false 15 | } 16 | 17 | const startPoint = getSelectionStartPoint(snapshot.context.selection) 18 | 19 | return comparePoints(snapshot, point, startPoint) === -1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin-input-rule/src/emoji-picker-rules.feature: -------------------------------------------------------------------------------- 1 | Feature: Emoji Picker Rules 2 | 3 | Scenario: Trigger Rule 4 | Given the text "" 5 | When ":" is typed 6 | Then the keyword is "" 7 | 8 | Scenario: Partial Keyword Rule 9 | Given the text "" 10 | When ":jo" is typed 11 | Then the keyword is "jo" 12 | 13 | Scenario: Keyword Rule 14 | Given the text "" 15 | When ":joy:" is typed 16 | Then the keyword is "joy" 17 | 18 | Scenario Outline: Consecutive keywords 19 | Given the text ":joy:" 20 | When is typed 21 | Then the keyword is 22 | 23 | Examples: 24 | | text | keyword | 25 | | ":" | "" | 26 | | ":cat" | "cat" | 27 | | ":cat:" | "cat" | 28 | -------------------------------------------------------------------------------- /apps/docs/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import {cn} from '@/lib/utils' 2 | import * as React from 'react' 3 | 4 | const Textarea = React.forwardRef< 5 | HTMLTextAreaElement, 6 | React.ComponentProps<'textarea'> 7 | >(({className, ...props}, ref) => { 8 | return ( 9 |