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;
};
|