Skip to content

Commit

Permalink
Add support for clipboardInputFilter/clipboardOutputFilter
Browse files Browse the repository at this point in the history
FEATURE: The new `EditorView.clipboardInputFilter` and `clipboardOutputFilter` facets
allow you to register filter functions that change text taken from or sent to the clipboard.
  • Loading branch information
marijnh committed Aug 24, 2024
1 parent b58ff33 commit 0a5bd27
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 5 deletions.
10 changes: 9 additions & 1 deletion src/editorview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {ViewUpdate, styleModule,
exceptionSink, updateListener, logException,
viewPlugin, ViewPlugin, PluginValue, PluginInstance, decorations, outerDecorations, atomicRanges,
scrollMargins, MeasureRequest, editable, inputHandler, focusChangeEffect, perLineTextDirection,
scrollIntoView, UpdateFlag, ScrollTarget, bidiIsolatedRanges, getIsolatedRanges, scrollHandler} from "./extension"
scrollIntoView, UpdateFlag, ScrollTarget, bidiIsolatedRanges, getIsolatedRanges, scrollHandler,
clipboardInputFilter, clipboardOutputFilter} from "./extension"
import {theme, darkTheme, buildTheme, baseThemeID, baseLightID, baseDarkID, lightDarkIDs, baseTheme} from "./theme"
import {DOMObserver} from "./domobserver"
import {Attrs, updateAttrs, combineAttrs} from "./attributes"
Expand Down Expand Up @@ -966,6 +967,13 @@ export class EditorView {
/// dispatching the custom behavior as a separate transaction.
static inputHandler = inputHandler

/// Functions provided in this facet will be used to transform text
/// pasted or dropped into the editor.
static clipboardInputFilter = clipboardInputFilter

/// Transform text copied or dragged from the editor.
static clipboardOutputFilter = clipboardOutputFilter

/// Scroll handlers can override how things are scrolled into view.
/// If they return `true`, no further handling happens for the
/// scrolling. If they return false, the default scroll behavior is
Expand Down
3 changes: 3 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export const inputHandler = Facet.define<(view: EditorView, from: number, to: nu

export const focusChangeEffect = Facet.define<(state: EditorState, focusing: boolean) => StateEffect<any> | null>()

export const clipboardInputFilter = Facet.define<(text: string, state: EditorState) => string>()
export const clipboardOutputFilter = Facet.define<(text: string, state: EditorState) => string>()

export const perLineTextDirection = Facet.define<boolean, boolean>({
combine: values => values.some(x => x)
})
Expand Down
17 changes: 13 additions & 4 deletions src/input.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {EditorSelection, EditorState, SelectionRange, RangeSet, Annotation, Text} from "@codemirror/state"
import {EditorSelection, EditorState, SelectionRange, RangeSet, Annotation, Text, Facet} from "@codemirror/state"
import {EditorView} from "./editorview"
import {ContentView} from "./contentview"
import {LineView} from "./blockview"
import {ViewUpdate, PluginValue, clickAddsSelectionRange, dragMovesSelection as dragBehavior, atomicRanges,
logException, mouseSelectionStyle, PluginInstance, focusChangeEffect, getScrollMargins} from "./extension"
logException, mouseSelectionStyle, PluginInstance, focusChangeEffect, getScrollMargins,
clipboardInputFilter, clipboardOutputFilter} from "./extension"
import browser from "./browser"
import {groupAt, skipAtomicRanges} from "./cursor"
import {getSelection, focusPreventScroll, Rect, dispatchKey, scrollableParents} from "./dom"
Expand Down Expand Up @@ -471,7 +472,13 @@ function capturePaste(view: EditorView) {
}, 50)
}

function textFilter(state: EditorState, facet: Facet<(value: string, state: EditorState) => string>, text: string) {
for (let filter of state.facet(facet)) text = filter(text, state)
return text
}

function doPaste(view: EditorView, input: string) {
input = textFilter(view.state, clipboardInputFilter, input)
let {state} = view, changes, i = 1, text = state.toText(input)
let byLine = text.lines == state.selection.ranges.length
let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString()
Expand Down Expand Up @@ -653,7 +660,8 @@ handlers.dragstart = (view, event: DragEvent) => {
inputState.draggedContent = range

if (event.dataTransfer) {
event.dataTransfer.setData("Text", view.state.sliceDoc(range.from, range.to))
event.dataTransfer.setData("Text", textFilter(view.state, clipboardOutputFilter,
view.state.sliceDoc(range.from, range.to)))
event.dataTransfer.effectAllowed = "copyMove"
}
return false
Expand All @@ -665,6 +673,7 @@ handlers.dragend = view => {
}

function dropText(view: EditorView, event: DragEvent, text: string, direct: boolean) {
text = textFilter(view.state, clipboardInputFilter, text)
if (!text) return
let dropPos = view.posAtCoords({x: event.clientX, y: event.clientY}, false)

Expand Down Expand Up @@ -764,7 +773,7 @@ function copiedRange(state: EditorState) {
linewise = true
}

return {text: content.join(state.lineBreak), ranges, linewise}
return {text: textFilter(state, clipboardOutputFilter, content.join(state.lineBreak)), ranges, linewise}
}

let lastLinewiseCopy: string | null = null
Expand Down

0 comments on commit 0a5bd27

Please sign in to comment.