{
  "version": 3,
  "sources": ["../../../../src/lib/utils/text/wrapSelection.ts"],
  "sourcesContent": ["import { Extension } from '@tiptap/core'\nimport { Mark, Node } from '@tiptap/pm/model'\nimport {\n\tAllSelection,\n\tEditorState,\n\tPlugin,\n\tPluginKey,\n\tSelection,\n\tTextSelection,\n\tTransaction,\n} from '@tiptap/pm/state'\nimport { getOwnProperty } from '@tldraw/editor'\nimport { isMonospace, isStraightQuote } from './literalTyping'\n\n// The pairs we know how to wrap a selection in, as `[opening, closing]`.\nconst WRAPPING_PAIRS = [\n\t// Quotes\n\t['\u2018', '\u2019'],\n\t['\u201C', '\u201D'],\n\t['\u2039', '\u203A'], // nested, French and Swiss\n\t['\u00AB', '\u00BB'], // French, Russian, Spanish, Greek\n\t['\u201E', '\u201D'], // Polish, Romanian, Hungarian\n\t['\u201A', '\u2019'], // nested, German and Polish\n\t['`', '`'],\n\t// Brackets\n\t['(', ')'],\n\t['[', ']'],\n\t['{', '}'],\n\t['<', '>'],\n\t// CJK\n\t['\u300C', '\u300D'], // corner quotes\n\t['\u300E', '\u300F'], // nested corner quotes\n\t['\u300A', '\u300B'], // title marks\n\t['\u3010', '\u3011'], // lenticular\n\t// Fullwidth forms, used alongside CJK text\n\t['\uFF08', '\uFF09'],\n\t['\uFF3B', '\uFF3D'],\n\t['\uFF5B', '\uFF5D'],\n\t['\uFF1C', '\uFF1E'],\n\t// Spanish\n\t['\u00A1', '!'],\n\t['\u00BF', '?'],\n] as const\n\n/**\n * The pairs the selection gets wrapped in, keyed by the character you type: with `hello` selected,\n * typing `(` gives `(hello)` and typing `\u00A1` gives `\u00A1hello!`.\n *\n * Either half of a pair wraps in the whole pair. `\u00A1` and `\u00BF` are out of reach on most keyboard\n * layouts, so typing the `!` or `?` that closes them has to work too.\n *\n * A straight quote wraps in the curly pair, because {@link https://tiptap.dev/docs/editor/extensions/functionality/typography | Typography}\n * turns a typed `\"` into `\u201C` or `\u201D` anyway. Inside code or monospace text it wraps in straight\n * quotes. Pass your own `pairs` to {@link WrapSelectionExtension} to wrap in straight quotes\n * everywhere, or to add pairs of your own.\n *\n * @public\n */\nexport const defaultWrappingPairs: Record<string, readonly [string, string]> = {\n\t// Closing characters first, so an opening one wins any character the two sides share, and in\n\t// reverse so that of two pairs closed by the same character \u2014 `\u201D` closes `\u201C \u201D` and `\u201E \u201D` \u2014 the\n\t// one listed first wins.\n\t...Object.fromEntries([...WRAPPING_PAIRS].reverse().map((pair) => [pair[1], pair])),\n\t...Object.fromEntries(WRAPPING_PAIRS.map((pair) => [pair[0], pair])),\n\t'\"': ['\u201C', '\u201D'],\n\t\"'\": ['\u2018', '\u2019'],\n}\n\n/**\n * Options for {@link WrapSelectionExtension}.\n *\n * @public\n */\nexport interface WrapSelectionOptions {\n\t/**\n\t * The `[opening, closing]` pair to wrap the selection in, keyed by the character typed.\n\t * Defaults to {@link defaultWrappingPairs}.\n\t */\n\tpairs: Record<string, readonly [string, string]>\n}\n\n/**\n * The marks every inline node in the range carries, or null if the range holds no inline content at\n * all \u2014 a selection of nothing but a block boundary, which there is no sense wrapping.\n */\nfunction getMarksSpanningRange(doc: Node, from: number, to: number): readonly Mark[] | null {\n\tlet marks: readonly Mark[] | null = null as readonly Mark[] | null\n\tdoc.nodesBetween(from, to, (node) => {\n\t\tif (!node.isInline) return\n\t\tmarks = marks ? marks.filter((mark) => mark.isInSet(node.marks)) : node.marks\n\t})\n\treturn marks\n}\n\nfunction isCodeMark(mark: Mark) {\n\treturn !!mark.type.spec.code\n}\n\n/**\n * The transaction that wraps the range in the pair `text` maps to, or null if this input isn't one\n * we wrap with.\n */\nfunction getWrapSelectionTransaction(\n\tstate: EditorState,\n\tfrom: number,\n\tto: number,\n\ttext: string,\n\tpairs: WrapSelectionOptions['pairs'],\n\tisMonospace: boolean\n): Transaction | null {\n\tif (from === to) return null\n\n\t// A backtick marks the selection as code, the way a closing backtick does to typed text, rather\n\t// than wrapping it in literal backticks. A schema without a code mark falls back to `pairs`.\n\tconst codeMark = text === '`' ? state.schema.marks.code : undefined\n\tconst pair = getOwnProperty(pairs, text)\n\tif (!codeMark && !pair) return null\n\n\t// Retyping a character over a selection of that same character leaves the DOM untouched, which\n\t// prosemirror-view reports as an input of the selected text itself. Wrapping it would turn a\n\t// selected `(` into `(()`.\n\tif (state.doc.textBetween(from, to) === text) return null\n\n\t// Select-all hands us an AllSelection, whose ends sit outside any text block. Without this it\n\t// declines, and select-all then `(` would replace the text that drag-selecting it wraps.\n\tif (state.selection instanceof AllSelection && from === 0 && to === state.doc.content.size) {\n\t\tfrom = Selection.atStart(state.doc).from\n\t\tto = Selection.atEnd(state.doc).to\n\t}\n\n\tconst $from = state.doc.resolve(from)\n\tconst $to = state.doc.resolve(to)\n\tif (!$from.parent.isTextblock || !$to.parent.isTextblock) return null\n\n\t// Marks common to the whole selection, so wrapping a bold word gives bold brackets while a\n\t// selection that starts bold and ends plain gets plain ones. Taking the marks at `from` alone\n\t// (what `marksAcross` returns) would leave a stray bold \u2014 or, worse, separately linked \u2014\n\t// closing character at the end.\n\tconst marks = getMarksSpanningRange(state.doc, from, to)\n\tif (!marks) return null\n\n\tif (codeMark) {\n\t\t// Toggles, so a backtick over code that's all code turns it back into plain text.\n\t\tconst tr = codeMark.isInSet(marks)\n\t\t\t? state.tr.removeMark(from, to, codeMark)\n\t\t\t: state.tr.addMark(from, to, codeMark.create())\n\t\treturn tr.scrollIntoView()\n\t}\n\tif (!pair) return null\n\n\tconst isCode =\n\t\tmarks.some(isCodeMark) || ($from.parent === $to.parent && !!$from.parent.type.spec.code)\n\t// A selection running from plain text into code, or out of it, would drop an unmarked delimiter\n\t// inside the code run and split it in two, so it's replaced as usual instead.\n\tif (\n\t\t!isCode &&\n\t\t($from.parent.type.spec.code ||\n\t\t\t$to.parent.type.spec.code ||\n\t\t\t$from.nodeAfter?.marks.some(isCodeMark) ||\n\t\t\t$to.nodeBefore?.marks.some(isCodeMark))\n\t) {\n\t\treturn null\n\t}\n\n\t// Code is literal and curly quotes look out of place in monospace text, so there a straight quote\n\t// wraps in straight quotes.\n\tconst [opening, closing] = (isCode || isMonospace) && isStraightQuote(text) ? [text, text] : pair\n\n\tconst tr = state.tr\n\t// Closing first: inserting at `from` would shift `to` out from under us.\n\ttr.insert(to, state.schema.text(closing, marks))\n\ttr.insert(from, state.schema.text(opening, marks))\n\t// Leave the original text selected, so wraps can be stacked: \u00ABhello\u00BB then \u201E\u00ABhello\u00BB\u201D.\n\ttr.setSelection(TextSelection.create(tr.doc, from + opening.length, to + opening.length))\n\n\treturn tr.scrollIntoView()\n}\n\n/**\n * Wraps the selected text in a pair of matching characters instead of replacing it: select `hello`,\n * press `\u00A1` or `!`, and you get `\u00A1hello!`. See {@link defaultWrappingPairs} for the pairs, which\n * can be replaced or extended through the extension's `pairs` option.\n *\n * A backtick is the exception: it toggles the `code` mark on the selection instead, when the schema\n * has one.\n *\n * @public\n */\nexport const WrapSelectionExtension = Extension.create<WrapSelectionOptions>({\n\tname: 'wrapSelection',\n\n\t// Input rules also run on a non-empty selection, and they run in extension priority order.\n\t// Without a priority above theirs, Typography's smart quote rule would get there first and\n\t// replace the selected text with a lone `\u201C`.\n\tpriority: 1000,\n\n\taddOptions() {\n\t\treturn { pairs: defaultWrappingPairs }\n\t},\n\n\taddProseMirrorPlugins() {\n\t\tconst { pairs } = this.options\n\t\treturn [\n\t\t\tnew Plugin({\n\t\t\t\tkey: new PluginKey('wrapSelection'),\n\t\t\t\tprops: {\n\t\t\t\t\thandleTextInput(view, from, to, text) {\n\t\t\t\t\t\t// Mid-composition the range is the IME's own text, not something the user selected.\n\t\t\t\t\t\tif (view.composing) return false\n\t\t\t\t\t\tconst tr = getWrapSelectionTransaction(\n\t\t\t\t\t\t\tview.state,\n\t\t\t\t\t\t\tfrom,\n\t\t\t\t\t\t\tto,\n\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\tpairs,\n\t\t\t\t\t\t\tfrom !== to && isStraightQuote(text) && isMonospace(view)\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (!tr) return false\n\t\t\t\t\t\tview.dispatch(tr)\n\t\t\t\t\t\treturn true\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t]\n\t},\n})\n"],
  "mappings": "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",
  "names": ["isMonospace", "tr"]
}
