jenkins-bot has submitted this change and it was merged.
Change subject: Autolink URLs when typing
......................................................................
Autolink URLs when typing
Create ve.ui.LinkAction with an `autolinkUrl` method which autolinks
a selected URL, and a RegExp-based ve.ui.Sequence which matches
autolink-able text.
Add new `unanchoredExternalLinkUrlProtocolsRegExp` which is like the
existing `externalLinkUrlProtocolsRegExp` except it is not anchored
to the start of the string. This allows you to execute searches for
possible links and/or add your own boundary conditions at the start
of the RegExp. Related change I26904c85cad5de45856f1e1eea45fa14cafcbfe3
adds support for this to ve.init.mw.Platform.
Change-Id: I21976ce0cc823af29bdd2a5c163476fea2408488
---
M build/modules.json
M demos/ve/desktop.html
M demos/ve/mobile.html
M src/init/sa/ve.init.sa.Platform.js
M src/init/ve.init.Platform.js
A src/ui/actions/ve.ui.LinkAction.js
M src/ui/ve.ui.CommandRegistry.js
M tests/index.html
A tests/ui/actions/ve.ui.LinkAction.test.js
9 files changed, 225 insertions(+), 1 deletion(-)
Approvals:
Cscott: Looks good to me, approved
Esanders: Looks good to me, approved
jenkins-bot: Verified
diff --git a/build/modules.json b/build/modules.json
index ff294a6..02718c4 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -405,6 +405,7 @@
"src/ui/actions/ve.ui.FormatAction.js",
"src/ui/actions/ve.ui.HistoryAction.js",
"src/ui/actions/ve.ui.IndentationAction.js",
+ "src/ui/actions/ve.ui.LinkAction.js",
"src/ui/actions/ve.ui.ListAction.js",
"src/ui/actions/ve.ui.TableAction.js",
"src/ui/actions/ve.ui.WindowAction.js",
@@ -558,6 +559,7 @@
"tests/ui/actions/ve.ui.AnnotationAction.test.js",
"tests/ui/actions/ve.ui.FormatAction.test.js",
"tests/ui/actions/ve.ui.IndentationAction.test.js",
+ "tests/ui/actions/ve.ui.LinkAction.test.js",
"tests/ui/actions/ve.ui.ListAction.test.js",
"tests/ui/actions/ve.ui.TableAction.test.js",
"tests/ui/datatransferhandlers/ve.ui.DSVFileTransferHandler.test.js",
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index 4974370..0a6289b 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -366,6 +366,7 @@
<script
src="../../src/ui/actions/ve.ui.FormatAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.HistoryAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.IndentationAction.js"></script>
+ <script src="../../src/ui/actions/ve.ui.LinkAction.js"></script>
<script src="../../src/ui/actions/ve.ui.ListAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.TableAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.WindowAction.js"></script>
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index de4f1b2..210d3b4 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -368,6 +368,7 @@
<script
src="../../src/ui/actions/ve.ui.FormatAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.HistoryAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.IndentationAction.js"></script>
+ <script src="../../src/ui/actions/ve.ui.LinkAction.js"></script>
<script src="../../src/ui/actions/ve.ui.ListAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.TableAction.js"></script>
<script
src="../../src/ui/actions/ve.ui.WindowAction.js"></script>
diff --git a/src/init/sa/ve.init.sa.Platform.js
b/src/init/sa/ve.init.sa.Platform.js
index aa83c61..76452b9 100644
--- a/src/init/sa/ve.init.sa.Platform.js
+++ b/src/init/sa/ve.init.sa.Platform.js
@@ -27,6 +27,7 @@
// Properties
this.externalLinkUrlProtocolsRegExp = /^https?\:\/\//;
+ this.unanchoredExternalLinkUrlProtocolsRegExp = /https?\:\/\//;
this.messagePaths = messagePaths || [];
this.parsedMessages = {};
this.userLanguages = [ 'en' ];
@@ -43,6 +44,11 @@
return this.externalLinkUrlProtocolsRegExp;
};
+/** @inheritdoc */
+ve.init.sa.Platform.prototype.getUnanchoredExternalLinkUrlProtocolsRegExp =
function () {
+ return this.unanchoredExternalLinkUrlProtocolsRegExp;
+};
+
/**
* Get message folder paths
*
diff --git a/src/init/ve.init.Platform.js b/src/init/ve.init.Platform.js
index 14cd008..674c8ff 100644
--- a/src/init/ve.init.Platform.js
+++ b/src/init/ve.init.Platform.js
@@ -99,7 +99,8 @@
/* Methods */
/**
- * Get a regular expression that matches allowed external link URLs.
+ * Get an anchored regular expression that matches allowed external link URLs
+ * starting at the beginning of an input string.
*
* @method
* @abstract
@@ -108,6 +109,16 @@
ve.init.Platform.prototype.getExternalLinkUrlProtocolsRegExp = null;
/**
+ * Get an unanchored regular expression that matches allowed external link URLs
+ * anywhere in an input string.
+ *
+ * @method
+ * @abstract
+ * @returns {RegExp} Regular expression object
+ */
+ve.init.Platform.prototype.getUnanchoredExternalLinkUrlProtocolsRegExp = null;
+
+/**
* Get a config value from the platform.
*
* @method
diff --git a/src/ui/actions/ve.ui.LinkAction.js
b/src/ui/actions/ve.ui.LinkAction.js
new file mode 100644
index 0000000..4e118a2
--- /dev/null
+++ b/src/ui/actions/ve.ui.LinkAction.js
@@ -0,0 +1,129 @@
+/*!
+ * VisualEditor UserInterface LinkAction class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Link action.
+ * This action transforms or inspects links (or potential links).
+ *
+ * @class
+ * @extends ve.ui.Action
+ * @constructor
+ * @param {ve.ui.Surface} surface Surface to act on
+ */
+ve.ui.LinkAction = function VeUiLinkAction( surface ) {
+ // Parent constructor
+ ve.ui.Action.call( this, surface );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.LinkAction, ve.ui.Action );
+
+/* Static Properties */
+
+ve.ui.LinkAction.static.name = 'link';
+
+/**
+ * RegExp matching an autolink + trailing space.
+ * @property {RegExp}
+ * @private
+ */
+ve.ui.LinkAction.static.autolinkRegExp = null; // Initialized below.
+
+/**
+ * List of allowed methods for the action.
+ *
+ * @static
+ * @property
+ */
+ve.ui.LinkAction.static.methods = [ 'autolinkUrl' ];
+
+/* Methods */
+
+/**
+ * Autolink the selection (which may have trailing whitespace).
+ *
+ * @method
+ * @return {boolean} Action was executed
+ */
+ve.ui.LinkAction.prototype.autolinkUrl = function () {
+ var range, rangeEnd, linktext, i,
+ surfaceModel = this.surface.getModel(),
+ documentModel = surfaceModel.getDocument(),
+ selection = surfaceModel.getSelection();
+
+ function isLinkAnnotation( annotation ) {
+ return /^link/.test( annotation.name );
+ }
+
+ if ( !( selection instanceof ve.dm.LinearSelection ) ) {
+ return false;
+ }
+
+ range = selection.getRange();
+ rangeEnd = range.end;
+
+ // Shrink range to eliminate trailing whitespace.
+ linktext = documentModel.data.getText( true, range ).replace( /\s+$/,
'' );
+ range = range.truncate( linktext.length );
+
+ // Check that none of the range has an existing link annotation.
+ // Otherwise we could autolink an internal link, which would be ungood.
+ for ( i = range.start; i < range.end; i++ ) {
+ if ( documentModel.data.getAnnotationsFromOffset( i
).containsMatching( isLinkAnnotation ) ) {
+ // Don't autolink this.
+ return false;
+ }
+ }
+
+ // Make sure `undo` doesn't expose the selected linktext.
+ surfaceModel.setLinearSelection( new ve.Range( rangeEnd, rangeEnd ) );
+
+ // Annotate the (previous) range.
+ surfaceModel.change(
+ ve.dm.Transaction.newFromAnnotation(
+ documentModel,
+ range,
+ 'set',
+ this.getLinkAnnotation( linktext )
+ ),
+ surfaceModel.getSelection()
+ );
+
+ return true;
+};
+
+/**
+ * Return an appropriate annotation for the given href.
+ *
+ * @method
+ * @return {ve.dm.LinkAnnotation} The annotation to use.
+ */
+ve.ui.LinkAction.prototype.getLinkAnnotation = function ( href ) {
+ return new ve.dm.LinkAnnotation( {
+ type: 'link',
+ attributes: {
+ href: href
+ }
+ } );
+};
+
+/* Registration */
+
+ve.ui.actionFactory.register( ve.ui.LinkAction );
+
+// Delayed initialization (wait until ve.init.platform exists)
+ve.init.Platform.static.initializedPromise.then( function () {
+
+ ve.ui.LinkAction.static.autolinkRegExp =
+ new RegExp(
+ '\\b' +
ve.init.platform.getUnanchoredExternalLinkUrlProtocolsRegExp().source +
'\\S+(\\s|\\n\\n)$'
+ );
+
+ ve.ui.sequenceRegistry.register(
+ new ve.ui.Sequence( 'autolinkUrl', 'autolinkUrl',
ve.ui.LinkAction.static.autolinkRegExp, 0, true )
+ );
+} );
diff --git a/src/ui/ve.ui.CommandRegistry.js b/src/ui/ve.ui.CommandRegistry.js
index 38d5d37..bdffba3 100644
--- a/src/ui/ve.ui.CommandRegistry.js
+++ b/src/ui/ve.ui.CommandRegistry.js
@@ -216,6 +216,12 @@
);
ve.ui.commandRegistry.register(
new ve.ui.Command(
+ 'autolinkUrl', 'link', 'autolinkUrl',
+ { supportedSelections: [ 'linear' ] }
+ )
+);
+ve.ui.commandRegistry.register(
+ new ve.ui.Command(
'pasteSpecial', 'content', 'pasteSpecial',
{ supportedSelections: [ 'linear', 'table' ] }
)
diff --git a/tests/index.html b/tests/index.html
index c979d43..bef18fc 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -295,6 +295,7 @@
<script src="../src/ui/actions/ve.ui.FormatAction.js"></script>
<script src="../src/ui/actions/ve.ui.HistoryAction.js"></script>
<script
src="../src/ui/actions/ve.ui.IndentationAction.js"></script>
+ <script src="../src/ui/actions/ve.ui.LinkAction.js"></script>
<script src="../src/ui/actions/ve.ui.ListAction.js"></script>
<script src="../src/ui/actions/ve.ui.TableAction.js"></script>
<script src="../src/ui/actions/ve.ui.WindowAction.js"></script>
@@ -409,6 +410,7 @@
<script
src="../tests/ui/actions/ve.ui.AnnotationAction.test.js"></script>
<script
src="../tests/ui/actions/ve.ui.FormatAction.test.js"></script>
<script
src="../tests/ui/actions/ve.ui.IndentationAction.test.js"></script>
+ <script
src="../tests/ui/actions/ve.ui.LinkAction.test.js"></script>
<script
src="../tests/ui/actions/ve.ui.ListAction.test.js"></script>
<script
src="../tests/ui/actions/ve.ui.TableAction.test.js"></script>
<script
src="../tests/ui/datatransferhandlers/ve.ui.DSVFileTransferHandler.test.js"></script>
diff --git a/tests/ui/actions/ve.ui.LinkAction.test.js
b/tests/ui/actions/ve.ui.LinkAction.test.js
new file mode 100644
index 0000000..5c92a34
--- /dev/null
+++ b/tests/ui/actions/ve.ui.LinkAction.test.js
@@ -0,0 +1,66 @@
+/*!
+ * VisualEditor UserInterface Actions LinkAction tests.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+QUnit.module( 've.ui.LinkAction' );
+
+/* Tests */
+
+function runAutolinkTest( assert, html, method, range, expectedRange,
expectedData, expectedOriginalData, msg ) {
+ var surface = ve.test.utils.createModelOnlySurfaceFromHtml( html ||
ve.dm.example.html ),
+ linkAction = new ve.ui.LinkAction( surface ),
+ data = ve.copy( surface.getModel().getDocument().getFullData()
),
+ originalData = ve.copy( data );
+
+ expectedData( data );
+ if ( expectedOriginalData ) {
+ expectedOriginalData( originalData );
+ }
+ surface.getModel().setLinearSelection( range );
+ linkAction[method]();
+
+ assert.equalLinearData( surface.getModel().getDocument().getFullData(),
data, msg + ': data models match' );
+ assert.equalRange( surface.getModel().getSelection().getRange(),
expectedRange, msg + ': ranges match' );
+
+ surface.getModel().undo();
+
+ assert.equalLinearData( surface.getModel().getDocument().getFullData(),
originalData, msg + ' (undo): data models match' );
+ assert.equalRange( surface.getModel().getSelection().getRange(),
expectedRange, msg + ' (undo): ranges match' );
+}
+
+QUnit.test( 'autolink', function ( assert ) {
+ var i,
+ cases = [
+ {
+ html: '<p>http://example.com xyz</p>',
+ range: new ve.Range( 1, 20 ),
+ method: 'autolinkUrl',
+ expectedRange: new ve.Range( 20, 20 ),
+ expectedData: function ( data ) {
+ for ( var i = 1; i < 19; i++ ) {
+ data[i] = [ data[i], [ 0 ] ];
+ }
+ },
+ msg: 'Autolink after space'
+ },
+ {
+ html: '<p>http://example.com</p><p>xyz</p>',
+ range: new ve.Range( 1, 21 ),
+ method: 'autolinkUrl',
+ expectedRange: new ve.Range( 21, 21 ),
+ expectedData: function ( data ) {
+ for ( var i = 1; i < 19; i++ ) {
+ data[i] = [ data[i], [ 0 ] ];
+ }
+ },
+ msg: 'Autolink after newline'
+ }
+ ];
+
+ QUnit.expect( cases.length * 4 );
+ for ( i = 0; i < cases.length; i++ ) {
+ runAutolinkTest( assert, cases[i].html, cases[i].method,
cases[i].range, cases[i].expectedRange, cases[i].expectedData,
cases[i].expectedOriginalData, cases[i].msg );
+ }
+} );
--
To view, visit https://gerrit.wikimedia.org/r/230145
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I21976ce0cc823af29bdd2a5c163476fea2408488
Gerrit-PatchSet: 8
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Cscott <[email protected]>
Gerrit-Reviewer: Cscott <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits