File: ve.ce.MWWikitextSurface.js

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (141 lines) | stat: -rw-r--r-- 5,045 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*!
 * VisualEditor DataModel Surface class.
 *
 * @copyright See AUTHORS.txt
 */

/**
 * DataModel surface.
 *
 * @class
 * @extends ve.ce.Surface
 *
 * @constructor
 * @param {ve.dm.Surface} model
 * @param {ve.ui.Surface} ui
 * @param {Object} [config]
 */
ve.ce.MWWikitextSurface = function VeCeMwWikitextSurface() {
	// Parent constructors
	ve.ce.MWWikitextSurface.super.apply( this, arguments );

	this.pasteTargetInput = new OO.ui.MultilineTextInputWidget();
};

/* Inheritance */

OO.inheritClass( ve.ce.MWWikitextSurface, ve.ce.Surface );

/**
 * @inheritdoc
 */
ve.ce.MWWikitextSurface.prototype.onCopy = function ( e ) {
	const clipboardData = e.originalEvent.clipboardData,
		text = this.getModel().getFragment().getText( true ).replace( /\n\n/g, '\n' );

	if ( !text ) {
		return;
	}

	if ( clipboardData ) {
		// Disable the default event so we can override the data
		e.preventDefault();
		clipboardData.setData( 'text/plain', text );
		// We're not going to set HTML, but for browsers that support custom data, set a clipboard key
		if ( ve.isClipboardDataFormatsSupported( e, true ) ) {
			const slice = this.model.documentModel.shallowCloneFromSelection( this.getModel().getSelection() );
			this.clipboardIndex++;
			const clipboardKey = this.clipboardId + '-' + this.clipboardIndex;
			this.clipboard = { slice: slice, hash: null };
			// Clone the elements in the slice
			slice.data.cloneElements( true );
			clipboardData.setData( 'text/xcustom', clipboardKey );

			// Explicitly store wikitext as text/x-wiki, so that wikitext-aware paste
			// contexts can accept it without having to do any content-
			// sniffing.
			clipboardData.setData( 'text/x-wiki', text );
		}
	} else {
		const originalSelection = new ve.SelectionState( this.nativeSelection );

		// Save scroll position before changing focus to "offscreen" paste target
		const scrollTop = this.surface.$scrollContainer.scrollTop();

		// Prevent surface observation due to native range changing
		this.surfaceObserver.disable();
		this.$pasteTarget.empty().append( this.pasteTargetInput.$element );
		this.pasteTargetInput.setValue( text ).select();

		// Restore scroll position after changing focus
		this.surface.$scrollContainer.scrollTop( scrollTop );

		// setTimeout: postpone until after the default copy action
		setTimeout( () => {
			// Change focus back
			this.$attachedRootNode[ 0 ].focus();
			this.showSelectionState( originalSelection );
			// Restore scroll position
			this.surface.$scrollContainer.scrollTop( scrollTop );
			this.surfaceObserver.clear();
			this.surfaceObserver.enable();
			// Detach input
			this.pasteTargetInput.$element.detach();
		} );
	}
};

/**
 * @inheritdoc
 */
ve.ce.MWWikitextSurface.prototype.afterPasteInsertExternalData = function ( targetFragment, pastedDocumentModel, contextRange ) {
	const wasSpecial = this.pasteSpecial,
		// TODO: This check returns true if the paste contains meaningful structure (tables, lists etc.)
		// but no annotations (bold, links etc.).
		wasPlain = wasSpecial || pastedDocumentModel.data.isPlainText( contextRange, true, undefined, true );

	const plainPastedDocumentModel = pastedDocumentModel.shallowCloneFromRange( contextRange );
	plainPastedDocumentModel.data.sanitize( { plainText: true, keepEmptyContentBranches: true } );
	// We just turned this into plaintext, which probably
	// affected the content-length. Luckily, because of
	// the earlier clone, we know we just want the whole
	// document, and because of the major change to
	// plaintext, the difference between originalRange and
	// balancedRange don't really apply. As such, clear
	// out newDocRange. (Can't just make it undefined;
	// need to exclude the internal list, and since we're
	// from a paste we also have to exclude the
	// opening/closing paragraph.)
	const plainContextRange = new ve.Range( plainPastedDocumentModel.getDocumentRange().from + 1, plainPastedDocumentModel.getDocumentRange().to - 1 );
	this.pasteSpecial = true;

	// isPlainText is true but we still need sanitize (e.g. remove lists)
	const promise = ve.ce.MWWikitextSurface.super.prototype.afterPasteInsertExternalData.call( this, targetFragment, plainPastedDocumentModel, plainContextRange );
	if ( !wasPlain ) {
		promise.then( () => {
			// We need to wait for the selection change after paste as that triggers
			// a contextChange event. Really we should wait for the afterPaste promise to resolve.
			setTimeout( () => {
				const surface = this.getSurface(),
					context = surface.getContext();
				// Ensure surface is deactivated on mobile so context can be shown (T336073)
				if ( context.isMobile() ) {
					surface.getView().deactivate();
				}
				context.addPersistentSource( {
					embeddable: false,
					name: 'wikitextPaste',
					data: {
						doc: pastedDocumentModel,
						contextRange: contextRange,
						fragment: targetFragment
					}
				} );
				surface.getModel().once( 'select', () => {
					context.removePersistentSource( 'wikitextPaste' );
				} );
			} );
		} );
	}
	return promise;
};