jenkins-bot has submitted this change and it was merged. Change subject: Create SourceSurfaceFragment for editing source documents ......................................................................
Create SourceSurfaceFragment for editing source documents Intercepts surface fragment modification calls and replaces content with source equivalent. Sub-classes can override convertDocument to convert content to a different source language (e.g. wikitext). Change-Id: I61f3ebc153f1e14201ba16dee25f3f062977d247 --- M .jsduck/categories.json M build/modules.json M demos/ve/desktop.html M demos/ve/mobile.html M src/ce/ve.ce.Surface.js M src/dm/lineardata/ve.dm.ElementLinearData.js A src/dm/ve.dm.SourceSurfaceFragment.js M src/ui/ve.ui.DesktopSurface.js M tests/dm/lineardata/ve.dm.ElementLinearData.test.js M tests/index.html 10 files changed, 154 insertions(+), 17 deletions(-) Approvals: Jforrester: Looks good to me, approved jenkins-bot: Verified diff --git a/.jsduck/categories.json b/.jsduck/categories.json index e5d9e10..53fbe89 100644 --- a/.jsduck/categories.json +++ b/.jsduck/categories.json @@ -60,7 +60,7 @@ "ve.dm.APIResultsProvider", "ve.dm.APIResultsQueue", "ve.dm.Surface", - "ve.dm.SurfaceFragment", + "ve.dm.*SurfaceFragment", "ve.dm.*Selection", "ve.dm.Transaction", "ve.dm.TransactionProcessor", diff --git a/build/modules.json b/build/modules.json index 653b73b..02a714a 100644 --- a/build/modules.json +++ b/build/modules.json @@ -257,6 +257,7 @@ "src/dm/ve.dm.Selection.js", "src/dm/ve.dm.Surface.js", "src/dm/ve.dm.SurfaceFragment.js", + "src/dm/ve.dm.SourceSurfaceFragment.js", "src/dm/ve.dm.DataString.js", "src/dm/ve.dm.Document.js", "src/dm/ve.dm.DocumentSlice.js", diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html index 551e458..5eb7bcb 100644 --- a/demos/ve/desktop.html +++ b/demos/ve/desktop.html @@ -213,6 +213,7 @@ <script src="../../src/dm/ve.dm.Selection.js"></script> <script src="../../src/dm/ve.dm.Surface.js"></script> <script src="../../src/dm/ve.dm.SurfaceFragment.js"></script> + <script src="../../src/dm/ve.dm.SourceSurfaceFragment.js"></script> <script src="../../src/dm/ve.dm.DataString.js"></script> <script src="../../src/dm/ve.dm.Document.js"></script> <script src="../../src/dm/ve.dm.DocumentSlice.js"></script> diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html index 1a64a20..e52ff99 100644 --- a/demos/ve/mobile.html +++ b/demos/ve/mobile.html @@ -214,6 +214,7 @@ <script src="../../src/dm/ve.dm.Selection.js"></script> <script src="../../src/dm/ve.dm.Surface.js"></script> <script src="../../src/dm/ve.dm.SurfaceFragment.js"></script> + <script src="../../src/dm/ve.dm.SourceSurfaceFragment.js"></script> <script src="../../src/dm/ve.dm.DataString.js"></script> <script src="../../src/dm/ve.dm.Document.js"></script> <script src="../../src/dm/ve.dm.DocumentSlice.js"></script> diff --git a/src/ce/ve.ce.Surface.js b/src/ce/ve.ce.Surface.js index 129c40c..2336fe7 100644 --- a/src/ce/ve.ce.Surface.js +++ b/src/ce/ve.ce.Surface.js @@ -2192,7 +2192,7 @@ // If the external HTML turned out to be plain text after // sanitization then run it as a plain text transfer item - if ( pastedDocumentModel.data.isPlainText( true, contextRange ) ) { + if ( pastedDocumentModel.data.isPlainText( contextRange, true ) ) { pastedText = pastedDocumentModel.data.getText( true, contextRange ); if ( pastedText ) { handled = this.handleDataTransferItems( diff --git a/src/dm/lineardata/ve.dm.ElementLinearData.js b/src/dm/lineardata/ve.dm.ElementLinearData.js index d687070..a8c4b3c 100644 --- a/src/dm/lineardata/ve.dm.ElementLinearData.js +++ b/src/dm/lineardata/ve.dm.ElementLinearData.js @@ -617,21 +617,28 @@ /** * Check if the data is just plain (un-annotated) text * - * @param {boolean} [allowNonContentNodes] Include non-content nodes in the definition of plain text, e.g. paragraphs, headings, lists * @param {ve.Range} [range] Range to get the data for. The whole data set if not specified. + * @param {boolean} [allowNonContentNodes] Include non-content nodes in the definition of plain text, e.g. paragraphs, headings, lists + * @param {boolean} [allowedTypes] Only allow specific non-content types * @return {boolean} The data is plain text */ -ve.dm.ElementLinearData.prototype.isPlainText = function ( allowNonContentNodes, range ) { - var i; +ve.dm.ElementLinearData.prototype.isPlainText = function ( range, allowNonContentNodes, allowedTypes ) { + var i, type; + range = range || new ve.Range( 0, this.getLength() ); for ( i = range.start; i < range.end; i++ ) { - if ( - typeof this.data[ i ] === 'string' || - allowNonContentNodes && this.isElementData( i ) && - !ve.dm.nodeFactory.isNodeContent( this.getType( i ) ) - ) { + if ( typeof this.data[ i ] === 'string' ) { continue; + } else if ( ( allowNonContentNodes || allowedTypes ) && this.isElementData( i ) ) { + type = this.getType( i ); + if ( allowedTypes && allowedTypes.indexOf( type ) !== -1 ) { + continue; + } + if ( allowNonContentNodes && !ve.dm.nodeFactory.isNodeContent( type ) ) { + continue; + } + return false; } else { return false; } diff --git a/src/dm/ve.dm.SourceSurfaceFragment.js b/src/dm/ve.dm.SourceSurfaceFragment.js new file mode 100644 index 0000000..4dfa738 --- /dev/null +++ b/src/dm/ve.dm.SourceSurfaceFragment.js @@ -0,0 +1,126 @@ +/*! + * VisualEditor DataModel SourceSurfaceFragment class. + * + * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org + */ + +/** + * Surface fragment for editing surfaces in source mode. + * + * @class + * @extends ve.dm.SurfaceFragment + * + * @constructor + * @param {ve.dm.Document} doc + */ +ve.dm.SourceSurfaceFragment = function VeDmSourceSurfaceFragment() { + // Parent constructors + ve.dm.SourceSurfaceFragment.super.apply( this, arguments ); +}; + +/* Inheritance */ + +OO.inheritClass( ve.dm.SourceSurfaceFragment, ve.dm.SurfaceFragment ); + +/** + * @inheritdoc + */ +ve.dm.SourceSurfaceFragment.prototype.annotateContent = function () { + var fragment, tempDocument, rangeInDocument, tempSurfaceModel, + originalDocument = this.getDocument(), + coveringRange = this.getSelection().getCoveringRange(); + + if ( coveringRange ) { + tempDocument = originalDocument.shallowCloneFromRange( coveringRange ); + rangeInDocument = tempDocument.originalRange; + } else { + tempDocument = new ve.dm.Document( + [ + { type: 'paragraph', internal: { generated: 'wrapper' } }, { type: '/paragraph' }, + { type: 'internalList' }, { type: '/internalList' } + ], + null, null, null, null, + originalDocument.getLang(), + originalDocument.getDir() + ); + rangeInDocument = new ve.Range( 1 ); + } + tempSurfaceModel = new ve.dm.Surface( tempDocument ); + fragment = tempSurfaceModel.getLinearFragment( rangeInDocument ); + fragment.annotateContent.apply( fragment, arguments ); + + this.insertDocument( fragment.getDocument() ); + + return this; +}; + +/** + * @inheritdoc + */ +ve.dm.SourceSurfaceFragment.prototype.insertContent = function ( content ) { + var data; + + if ( typeof content !== 'string' ) { + data = new ve.dm.ElementLinearData( new ve.dm.IndexValueStore(), content ); + if ( !data.isPlainText( null, false, [ 'paragraph' ] ) ) { + this.insertDocument( new ve.dm.Document( content.concat( [ { type: 'internalList' }, { type: '/internalList' } ] ) ) ); + return this; + } + } + + // Parent method + return ve.dm.SourceSurfaceFragment.super.prototype.insertContent.call( this, content ); +}; + +/** + * @inheritdoc + */ +ve.dm.SourceSurfaceFragment.prototype.insertDocument = function ( doc, newDocRange ) { + var conversionPromise, + range = this.getSelection().getCoveringRange(), + fragment = this; + + if ( !range ) { + return this; + } + + newDocRange = newDocRange || new ve.Range( 0, doc.getInternalList().getListNode().getOuterRange().start ); + + if ( doc.data.isPlainText( newDocRange, false, [ 'paragraph' ] ) ) { + return ve.dm.SourceSurfaceFragment.super.prototype.insertContent.call( this, doc.data.getDataSlice( newDocRange ) ); + } + + conversionPromise = this.convertDocument( doc ).always( function ( source ) { + fragment.removeContent(); + + if ( source ) { + // Parent method + ve.dm.SourceSurfaceFragment.super.prototype.insertContent.call( fragment, source.trim() ); + } + } ); + + return this; +}; + +/** + * Convert sub document to source text + * + * The default implementation converts to HTML synchronously. + * + * If the conversion is asynchornous it should lock the surface + * until complete. + * + * @param {ve.dm.Document} doc Document + * @return {jQuery.Promise} Promise with resolves with source, or rejects + */ +ve.dm.SourceSurfaceFragment.prototype.convertDocument = function ( doc ) { + if ( !doc.data.hasContent() ) { + return $.Deferred().reject().promise(); + } else { + return $.Deferred().resolve( + ve.properInnerHtml( + ve.dm.converter.getDomFromModel( doc ).body + ) + ).promise(); + } +}; diff --git a/src/ui/ve.ui.DesktopSurface.js b/src/ui/ve.ui.DesktopSurface.js index a7d826a..aaeca50 100644 --- a/src/ui/ve.ui.DesktopSurface.js +++ b/src/ui/ve.ui.DesktopSurface.js @@ -6,7 +6,7 @@ /** * A surface is a top-level object which contains both a surface model and a surface view. - * This is the mobile version of the surface. + * This is the desktop version of the surface. * * @class * @extends ve.ui.Surface diff --git a/tests/dm/lineardata/ve.dm.ElementLinearData.test.js b/tests/dm/lineardata/ve.dm.ElementLinearData.test.js index 89e4c32..9d11025 100644 --- a/tests/dm/lineardata/ve.dm.ElementLinearData.test.js +++ b/tests/dm/lineardata/ve.dm.ElementLinearData.test.js @@ -802,12 +802,12 @@ QUnit.test( 'isPlainText', 6, function ( assert ) { var doc = ve.dm.example.createExampleDocument(); - assert.strictEqual( doc.data.isPlainText( false, new ve.Range( 1, 2 ) ), true, 'Plain text' ); - assert.strictEqual( doc.data.isPlainText( true, new ve.Range( 1, 3 ) ), false, 'Annotated text' ); - assert.strictEqual( doc.data.isPlainText( false, new ve.Range( 9, 11 ) ), false, 'Paragraph and text (no content nodes)' ); - assert.strictEqual( doc.data.isPlainText( true, new ve.Range( 9, 11 ) ), true, 'Paragraph and text (content nodes allowed)' ); - assert.strictEqual( doc.data.isPlainText( false, new ve.Range( 12, 26 ) ), false, 'List (no content nodes)' ); - assert.strictEqual( doc.data.isPlainText( true, new ve.Range( 12, 26 ) ), true, 'List (content nodes allowed)' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 1, 2 ), false ), true, 'Plain text' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 1, 3 ), true ), false, 'Annotated text' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 9, 11 ), false ), false, 'Paragraph and text (no content nodes)' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 9, 11 ), true ), true, 'Paragraph and text (content nodes allowed)' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 12, 26 ), false ), false, 'List (no content nodes)' ); + assert.strictEqual( doc.data.isPlainText( new ve.Range( 12, 26 ), true ), true, 'List (content nodes allowed)' ); } ); QUnit.test( 'getText', 4, function ( assert ) { diff --git a/tests/index.html b/tests/index.html index e85fe35..12f2604 100644 --- a/tests/index.html +++ b/tests/index.html @@ -139,6 +139,7 @@ <script src="../src/dm/ve.dm.Selection.js"></script> <script src="../src/dm/ve.dm.Surface.js"></script> <script src="../src/dm/ve.dm.SurfaceFragment.js"></script> + <script src="../src/dm/ve.dm.SourceSurfaceFragment.js"></script> <script src="../src/dm/ve.dm.DataString.js"></script> <script src="../src/dm/ve.dm.Document.js"></script> <script src="../src/dm/ve.dm.DocumentSlice.js"></script> -- To view, visit https://gerrit.wikimedia.org/r/298285 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I61f3ebc153f1e14201ba16dee25f3f062977d247 Gerrit-PatchSet: 10 Gerrit-Project: VisualEditor/VisualEditor Gerrit-Branch: master Gerrit-Owner: Esanders <esand...@wikimedia.org> Gerrit-Reviewer: DLynch <dly...@wikimedia.org> Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits