Jsahleen has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/181374

Change subject: [WIP] Red Links: Adding red link functionality
......................................................................

[WIP] Red Links: Adding red link functionality

* Red links are marked with grey and dashed underline
* Clicking on marker brings up "Mark link" option
* After being marked as missing links, can be reverted.
* Improvements to isValidSelection to prevent links inside links
* Note: Significant refactoring was necessary to make this work.

Change-Id: I6bce4bdd4688a5fb20c3268561329b126e57c3c7
---
M Resources.php
M i18n/en.json
M i18n/qqq.json
M modules/publish/ext.cx.publish.js
M modules/tools/ext.cx.tools.link.js
M modules/tools/styles/ext.cx.tools.link.less
6 files changed, 427 insertions(+), 116 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation 
refs/changes/74/181374/1

diff --git a/Resources.php b/Resources.php
index f758a3f..682c039 100644
--- a/Resources.php
+++ b/Resources.php
@@ -347,6 +347,10 @@
                'cx-tools-link-add',
                'cx-tools-link-remove',
                'cx-tools-link-instruction-shortcut',
+               'cx-tools-missing-link-title',
+               'cx-tools-missing-link-text',
+               'cx-tools-missing-link-tooltip',
+               'cx-tools-missing-link-mark-link'
        ),
        'dependencies' => array(
                'ext.cx.tools.manager',
diff --git a/i18n/en.json b/i18n/en.json
index e66d5e2..bc833a9 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -122,5 +122,9 @@
        "cx-save-draft-tooltip": "Translation drafts are saved automatically",
        "cx-contributions": "New contribution",
        "cx-contributions-translation": "Translation",
-       "cx-contributions-media": "Upload media file"
+       "cx-contributions-media": "Upload media file",
+       "cx-tools-missing-link-title": "Missing link",
+       "cx-tools-missing-link-text": "Mark missing pages to encourage their 
creation",
+       "cx-tools-missing-link-tooltip": "Translate (in new window)",
+       "cx-tools-missing-link-mark-link": "Mark as missing"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 17b2cb5..1efd8f6 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -125,5 +125,9 @@
        "cx-save-draft-tooltip": "Tooltip text shown for the save status 
indicator text in the header of [[Special:ContentTranslation]].\n\nParameters: 
\n* $1 - the number of minutes ago the translation was saved.",
        "cx-contributions": "Text of a button which opens a dropdown",
        "cx-contributions-translation": "Dropdown 
item\n{{Identical|Translation}}",
-       "cx-contributions-media": "Dropdown item"
+       "cx-contributions-media": "Dropdown item",
+       "cx-tools-missing-link-title": "Title for target link card when card is 
used for working with missing links",
+       "cx-tools-missing-link-text": "Message with instructions for marking 
links as missing",
+       "cx-tools-missing-link-tooltip": "Tooltip that shows when hovering over 
target link card link when working with missing links.\nClicking on link opens 
a new translation view for the missing link.",
+       "cx-tools-missing-link-mark-link": "Text for mark as missing link 
control"
 }
diff --git a/modules/publish/ext.cx.publish.js 
b/modules/publish/ext.cx.publish.js
index 00ca9c7..05c16d0 100644
--- a/modules/publish/ext.cx.publish.js
+++ b/modules/publish/ext.cx.publish.js
@@ -196,6 +196,9 @@
                        }
                } );
 
+               // Remove any remaining red link markers
+               $content.find( '.cx-redlink-marker' ).remove();
+
                return $content.html();
        }
 
diff --git a/modules/tools/ext.cx.tools.link.js 
b/modules/tools/ext.cx.tools.link.js
index ad93c46..f3df7d4 100644
--- a/modules/tools/ext.cx.tools.link.js
+++ b/modules/tools/ext.cx.tools.link.js
@@ -21,9 +21,11 @@
                this.$card = null;
                this.$removeLink = null;
                this.$addLink = null;
+               this.$markLink = null;
                this.$link = null;
                this.$sourceLinkCard = null;
                this.$targetLinkCard = null;
+               this.$redLinkCard = null;
 
                this.siteMapper = siteMapper;
        }
@@ -104,8 +106,11 @@
                this.$addLink = $( '<div>' )
                        .addClass( 'card__add-link' )
                        .text( mw.msg( 'cx-tools-link-add' ) );
+               this.$markLink = $( '<div>' )
+                       .addClass( 'card__mark-link' )
+                       .text( mw.msg( 'cx-tools-missing-link-mark-link' ) );
                this.$targetLinkCard.find( '.card__link-info' )
-                       .append( this.$addLink, this.$removeLink );
+                       .append( this.$addLink, this.$removeLink, 
this.$markLink );
 
                return this.$targetLinkCard.hide();
        };
@@ -290,20 +295,32 @@
         * @return {boolean}
         */
        function isValidSelection( selection ) {
-               var $parent, $parentSection;
+               var $parent;
 
-               if ( !selection || !selection.toString().length ) {
+               if ( !selection ) {
                        return false;
                }
                $parent = getSelectionParent();
 
-               if ( $parent.is( '[contenteditable="false"]' ) ) {
+               if ( $parent.is( '[contenteditable="false"]' ) ||
+                       $parent.parents( '[contenteditable="false"]' ).length > 0
+               ) {
                        return false;
                }
-               // Get parent section
-               $parentSection = $parent.parents( '[contenteditable]' );
-               // Check if that section is editable
-               return $parentSection.prop( 'contenteditable' );
+
+               if ( $parent.is( '.cx-target-link' ) ||
+                       $parent.parents( '.cx-target-link' ).length > 0
+               ) {
+                       return false;
+               }
+
+               if ( $parent.is( '.cx-redlink-marker' ) ||
+                       $parent.parents( '.cx-redlink-marker' ).length > 0
+               ) {
+                       return false;
+               }
+
+               return true;
        }
 
        /**
@@ -323,7 +340,8 @@
                        .text( linkText )
                        .attr( {
                                href: title,
-                               rel: 'mw:WikiLink'
+                               rel: 'mw:WikiLink',
+                               title: title
                        } );
 
                if ( id ) {
@@ -340,18 +358,55 @@
        };
 
        /**
-        * Make the given element to be an external link to a page in given 
language.
-        * @param {jQuery} $link Link element
+        * Create an external link to a page in given language.
         * @param {string} target The page title in the target wikis
         * @param {string} language Language code of target wikis
+        * @return {jQuery}
         */
-       LinkCard.prototype.createExternalLink = function ( $link, target, 
language ) {
+       LinkCard.prototype.createExternalLink = function ( target, language ) {
                // Normalize the text for display and href
-               var title = mw.Title.newFromText( target );
+               var $link,
+                       title = mw.Title.newFromText( target );
+
+               $link = $( '<a>' )
+                       .addClass( 'card__link-text' );
 
                title = title ? title.getPrefixedText() : target;
 
                this.setLinkAttributes( $link, title, language );
+
+               return $link;
+       };
+
+       /**
+        * Create a link to a new translation view for the source and target 
titles.
+        * @param {string} sourceTitle The title of the article to be translated
+        * @param {string} targetTitle The title for the translated article
+        * @return {jQuery}
+        */
+       LinkCard.prototype.createTranslationLink = function ( sourceTitle ) {
+               var $link, language, uri;
+
+               language = mw.config.get( 'wgContentLanguage' );
+
+               uri = new mw.Uri().extend( {
+                       from: mw.cx.sourceLanguage,
+                       to: mw.cx.targetLanguage,
+                       page: sourceTitle,
+                       targetttitle: sourceTitle
+               } );
+
+               $link = $( '<a>' )
+                       .attr( {
+                               href: uri.toString(),
+                               target: '_blank',
+                               lang: language,
+                               dir: $.uls.data.getDir( language ),
+                               title: sourceTitle
+                       } ).text( sourceTitle )
+                       .addClass( 'cx-red-link' );
+
+               return $link;
        };
 
        /**
@@ -369,23 +424,19 @@
 
                api = this.siteMapper.getApi( language );
                getLink( api, title, language ).done( function ( response ) {
-                       var imgSrc, pageId, page;
+                       var imgSrc, pageId, page, $externalLink;
 
                        pageId = Object.keys( response.query.pages )[ 0 ];
                        if ( pageId === '-1' ) {
-                               if ( linkCard.$link ) {
-                                       linkCard.$link.addClass( 'new' );
-                               }
-
                                return;
                        }
 
                        page = response.query.pages[ pageId ];
-                       linkCard.createExternalLink(
-                               linkCard.$sourceLinkCard.find( 
'.card__link-text' ),
-                               page.title,
-                               language
-                       );
+                       $externalLink = linkCard.createExternalLink( 
page.title, language );
+
+                       linkCard.$sourceLinkCard.find( '.card__link-container' )
+                               .empty()
+                               .append( $externalLink );
 
                        if ( page.thumbnail ) {
                                imgSrc = page.thumbnail.source;
@@ -404,7 +455,7 @@
         * @param {string} title The title
         * @param {string} language Target language code
         */
-       LinkCard.prototype.prepareTargetLinkCard = function ( title, language ) 
{
+       LinkCard.prototype.prepareTargetLinkCard = function ( title, language, 
linkId ) {
                var api,
                        linkCard = this;
 
@@ -412,64 +463,86 @@
                        return;
                }
 
+               this.$addLink.hide();
+               this.$removeLink.hide();
+               this.$markLink.hide();
+
                api = this.siteMapper.getApi( language );
                getLink( api, title, language ).done( function ( response ) {
-                       var pageId, page, selection, imgSrc;
+                       var pageId, page, selection, sourceTitle, imgSrc, 
$externalLink,
+                               $translationLink, $missingLinkMessage;
 
+                       selection = mw.cx.selection.get();
                        pageId = Object.keys( response.query.pages )[ 0 ];
-                       if ( pageId === '-1' ) {
+
+                       if ( pageId !== '-1' ) {
+                               // Link was found.
+                               page = response.query.pages[ pageId ];
+
+                               // Set title
+                               $( '.card__title' )
+                                       .text( mw.msg( 'cx-tools-link-title' ) 
);
+
+                               // Create external link and add to card
+                               $externalLink = linkCard.createExternalLink( 
page.title, language );
+                               linkCard.$targetLinkCard.find( 
'.card__link-container' )
+                                       .empty()
+                                       .append( $externalLink );
+
+                               if ( linkCard.isSourceLink() && 
isValidSelection( selection ) ) {
+                                       // Link is a source link and the 
selection is valid
+                                       // Enable add link with link id.
+                                       linkCard.enableAddLink( selection, 
page.title, linkId );
+                               } else if ( linkCard.isEditableTargetLink() ) {
+                                       // Link is an editable target link. 
Enable remove link.
+                                       linkCard.enableRemoveLink();
+                               } else if ( isValidSelection( selection ) ) {
+                                       // Link came from text selection or 
search
+                                       // Enable add link with no linkId
+                                       linkCard.enableAddLink( selection, 
page.title );
+                               }
+
+                               // Add the page thumbnail if it exists.
+                               if ( page.thumbnail ) {
+                                       imgSrc = page.thumbnail.source;
+                                       linkCard.$targetLinkCard.find( 
'.card__link-image-container' )
+                                               .append( $( '<img>' ).attr( 
'src', imgSrc ) );
+                               }
+                       } else if ( linkId ) {
+                               // No link was found. This is a red link.
+
+                               // Set title
+                               linkCard.$targetLinkCard.find( '.card__title' )
+                                       .text( mw.msg( 
'cx-tools-missing-link-title' ) );
+
+                               if ( linkCard.isRedLink() ) {
+                                       // There is an existing redlink
+                                       sourceTitle = linkCard.getSourceLink( 
linkId ).attr( 'title' );
+
+                                       // Create link to translate page in new 
window and add to card
+                                       $translationLink = 
linkCard.createTranslationLink( sourceTitle );
+                                       linkCard.$targetLinkCard.find( 
'.card__link-container' )
+                                               .empty()
+                                               .append( $translationLink );
+
+                                       // Enable remove link for red links
+                                       linkCard.enableRemoveRedLink();
+                               } else {
+                                       // Link is a red link marker
+                                       // Create missing link message and add 
to card.
+                                       $missingLinkMessage = $( '<p>' )
+                                               .addClass( 
'card__missing-link-message' )
+                                               .text( mw.msg( 
'cx-tools-missing-link-text' ) );
+                                       linkCard.$targetLinkCard.find( 
'.card__link-container' )
+                                               .empty()
+                                               .append( $missingLinkMessage );
+
+                                       // Enable mark link.
+                                       linkCard.enableMarkLink( title, linkId 
);
+                               }
+                       } else {
                                linkCard.$targetLinkCard.hide();
                                return;
-                       }
-                       page = response.query.pages[ pageId ];
-                       selection = mw.cx.selection.get();
-
-                       linkCard.createExternalLink(
-                               linkCard.$targetLinkCard.find( 
'.card__link-text' ),
-                               page.title,
-                               language
-                       );
-
-                       if ( isValidSelection( selection ) ) {
-                               // Some text selected in translation column and 
it has a link.
-                               // Set up the add link button.
-                               linkCard.$addLink.click( function () {
-                                       mw.cx.selection.restore( 'translation' 
);
-                                       linkCard.createInternalLink( 
selection.toString(), page.title );
-                               } );
-
-                               // Show the add link button
-                               linkCard.$addLink.show();
-
-                               // Hide the remove link button
-                               linkCard.$removeLink.hide();
-                       } else {
-                               // Nothing selected. Hide the add link button.
-                               linkCard.$addLink.hide();
-                               linkCard.$removeLink.click( function () {
-                                       mw.cx.selection.restore( 'translation' 
);
-                                       linkCard.removeLink();
-                               } );
-                       }
-
-                       if ( !linkCard.$link ) {
-                               // There is no link to remove. Card came from 
search.
-                               // Prepare add link
-                               linkCard.$addLink.click( function () {
-                                       mw.cx.selection.restore( 'translation' 
);
-                                       linkCard.createInternalLink( 
selection.toString(), page.title );
-                               } );
-
-                               // Show the add link button
-                               linkCard.$addLink.show();
-                               // Hide the remove link button
-                               linkCard.$removeLink.hide();
-                       }
-
-                       if ( page.thumbnail ) {
-                               imgSrc = page.thumbnail.source;
-                               linkCard.$targetLinkCard.find( 
'.card__link-image-container' )
-                                       .append( $( '<img>' ).attr( 'src', 
imgSrc ) );
                        }
 
                        linkCard.$targetLinkCard.show();
@@ -483,27 +556,19 @@
        };
 
        /**
-        * For the link, get the corresponding link in source section.
+        * For the id, get the corresponding link in source section.
         * @return {jQuery}
         */
-       LinkCard.prototype.getSourceLink = function () {
-               if ( !this.$link ) {
-                       return null;
-               }
-
-               return $( '.cx-link[data-linkid="' + this.$link.data( 'linkid' 
) + '"]' );
+       LinkCard.prototype.getSourceLink = function ( id ) {
+               return $( '.cx-link[data-linkid="' + id + '"]' );
        };
 
        /**
-        * For the link, get the corresponding link in target section.
+        * For the id, get the corresponding link in target section.
         * @return {jQuery}
         */
-       LinkCard.prototype.getTargetLink = function () {
-               if ( !this.$link ) {
-                       return null;
-               }
-
-               return $( '.cx-target-link[data-linkid="' + this.$link.data( 
'linkid' ) + '"]' );
+       LinkCard.prototype.getTargetLink = function ( id ) {
+               return $( '.cx-target-link[data-linkid="' + id + '"]' );
        };
 
        /**
@@ -550,6 +615,111 @@
        };
 
        /**
+        * Check if the link, the click of which caused the current link card
+        * is a red link.
+        * @return {boolean}
+        */
+       LinkCard.prototype.isRedLink = function () {
+               if ( !this.$link ) {
+                       return false;
+               }
+
+               return this.$link.is( '.cx-red-link' );
+       };
+
+       /**
+        * Enables the add link control for adapted links
+        * @param {selection} selection The captured selection
+        * @param {string} title The title to be used for the new link
+        * @param {string} linkId The link id to be added to the new link
+        */
+       LinkCard.prototype.enableAddLink = function ( selection, title, linkId 
) {
+               var linkCard = this;
+
+               this.$addLink.click( function () {
+                       mw.cx.selection.restore( 'translation' );
+                       if ( selection.toString().length > 0 ) {
+                               linkCard.createInternalLink( 
selection.toString(), title, linkId );
+                       } else {
+                               linkCard.createInternalLink( title, title, 
linkId );
+                       }
+                       mw.cx.selection.restore( 'translation' );
+               } );
+
+               this.$addLink.show();
+       };
+
+       /**
+        * Enables the mark link control for missing links
+        * @param {string} title The title to be used for the new link
+        * @param {string} linkId The link id to be added to the new link
+        */
+       LinkCard.prototype.enableMarkLink = function ( title, linkId ) {
+               var $marker, $redLink;
+
+               $marker = $( '.cx-redlink-marker[data-linkid="' + linkId + '"]' 
);
+
+               $redLink = $( '<a>' )
+                       .prop( 'href', getValidTitle( title ) )
+                       .html( $marker.html() )
+                       .attr( {
+                               'rel': 'mw:WikiLink',
+                               'data-linkid': linkId,
+                               'title': title
+                       } )
+                       .addClass( 'cx-target-link cx-red-link' );
+
+               this.$markLink.click( function () {
+                       mw.cx.selection.restore( 'translation' );
+                       $marker.before( $redLink ).remove();
+                       mw.cx.selection.restore( 'translation' );
+               } );
+
+               this.$markLink.show();
+       };
+
+       /**
+        * Enables the remove link control for adapted links
+        * @param {selection} selection The captured selection
+        * @param {string} title The title to be used for the new link
+        * @param {string} linkId The link id to be added to the new link
+        */
+       LinkCard.prototype.enableRemoveLink = function () {
+               var linkCard = this;
+
+               this.$removeLink.click( function () {
+                       mw.cx.selection.restore( 'translation' );
+                       linkCard.removeLink();
+                       mw.cx.selection.restore( 'translation' );
+               } );
+
+               this.$removeLink.show();
+       };
+
+       /**
+        * Enables the remove link control for red links
+        */
+       LinkCard.prototype.enableRemoveRedLink = function () {
+               var $marker,
+                       linkCard = this;
+
+               $marker = $( '<span>' )
+                       .addClass( 'cx-redlink-marker 
cx-redlink-marker-highlight' )
+                       .attr( 'data-linkid', this.$link.attr( 'data-linkid' ) )
+                       .html( this.$link.html() )
+                       .click( markerClickHandler );
+
+               // Set up the remove link button
+               this.$removeLink.click( function () {
+                       mw.cx.selection.restore( 'translation' );
+                       linkCard.$link.before( $marker ).remove();
+                       mw.cx.selection.restore( 'translation' );
+               } );
+
+               this.$removeLink.show();
+       };
+
+       /**
         * Get a valid normalized title from the given text
         * If the text is not suitable for the title, return null;
         * Validation is done by mw.Title
@@ -572,7 +742,9 @@
         * @param {string} [language] The language where the link points to.
         */
        LinkCard.prototype.start = function ( link, language ) {
-               var selection, title, targetTitle, sourceTitle, $targetLink, 
$sourceLink;
+               var selection, title, linkId, targetTitle, sourceTitle,
+                       $targetLink, $sourceLink,
+                       linkCard = this;
 
                // If language is not given, use target language
                language = language || mw.cx.targetLanguage;
@@ -580,50 +752,117 @@
                // Capture the current selection
                selection = mw.cx.selection.get();
 
-               // If the link is a source link, restore the selection
+               // If the click was on a source link, restore the selection
                // in the translation column
                if ( language === mw.cx.sourceLanguage ) {
                        mw.cx.selection.restore( 'translation' );
                }
 
-               // link can be link text or jQuery link object
+               // The link can be a string, a jQuery link or a jQuery span.
                if ( typeof link === 'string' ) {
+                       // link came from selection of search; just set title
                        title = getValidTitle( link );
-               } else {
+               } else if ( link.is( '.cx-link' ) || link.is( '.cx-target-link' 
) ) {
+                       // An existing source or target link was clicked.
+                       // Set the title and get the linkId. Store the link.
                        title = cleanupLinkHref( link.attr( 'href' ) );
                        this.$link = link;
+                       linkId = link.attr( 'data-linkid' );
+               } else if ( link.is( '.cx-redlink-marker' ) ) {
+                       // The click was on a red link marker span.
+                       // Set the title and linkId
+                       title = link.text();
+                       linkId = link.attr( 'data-linkid' );
                }
 
-               // Do we have a valid title now?
+               // If we don't have a valid title, exit.
                if ( !title ) {
                        this.stop();
                        return;
                }
 
+               // UI highlighting
                this.highlightLink();
-               if ( this.$link && language === mw.cx.targetLanguage ) {
+
+               // If the click was on a target link, use the title
+               // as the target title. Otherwise, use the title as
+               // the source title.
+               if ( language === mw.cx.targetLanguage ) {
                        targetTitle = title;
                } else {
                        sourceTitle = title;
                }
 
-               if ( !targetTitle ) {
-                       $targetLink = this.getTargetLink();
-                       targetTitle = $targetLink && $targetLink.attr( 'href' ) 
|| title;
-               }
-
+               // If the click was on a target link or red link marker,
+               // we have no source title. Get the source link and create
+               // a source title from its href attribute
                if ( !sourceTitle ) {
-                       $sourceLink = this.getSourceLink();
+                       $sourceLink = this.getSourceLink( linkId );
                        sourceTitle = $sourceLink.attr( 'href' );
                        sourceTitle = cleanupLinkHref( sourceTitle );
                }
 
-               this.prepareSourceLinkCard( sourceTitle, mw.cx.sourceLanguage );
-               this.prepareTargetLinkCard( targetTitle, mw.cx.targetLanguage );
+               // If the click was on a source link and valid text is selected
+               // create a new internal target link by adapting the source 
title.
+               // Don't bother setting up the link cards
+               if ( this.isSourceLink() &&
+                       isValidSelection( selection ) &&
+                       selection.toString().length > 0
+               ) {
+                       this.adapt( sourceTitle, mw.cx.targetLanguage )
+                               .done( function ( adaptations ) {
+                                       if ( adaptations[ sourceTitle ] ) {
+                                               $targetLink = 
linkCard.createInternalLink(
+                                                       selection.toString(),
+                                                       adaptations[ 
sourceTitle ],
+                                                       linkId
+                                               );
+                                       }
+                               } );
+                       return;
+               }
 
-               // If text is selected, create a new internal link
-               if ( isValidSelection( selection ) && this.$link ) {
-                       $targetLink = this.createInternalLink( 
selection.toString(), targetTitle, this.$link.data( 'linkid' ) );
+               // If no target text is selected, set up the source link card
+               this.prepareSourceLinkCard( sourceTitle, mw.cx.sourceLanguage );
+
+               // If the click was on a source link, we have no target title.
+               if ( !targetTitle ) {
+                       // Check to see if there is an existing target link.
+                       $targetLink = this.getTargetLink( linkId );
+
+                       // If there is an existing target link, create a target 
title
+                       // from the href and set up the target link card.
+                       if ( $targetLink && $targetLink.is( '.cx-target-link' ) 
) {
+                               targetTitle = $targetLink.attr( 'href' );
+                               this.prepareTargetLinkCard(
+                                       targetTitle,
+                                       mw.cx.targetLanguage,
+                                       linkId
+                               );
+                       } else {
+                               // If there is not existing target link, adapt 
the source title
+                               // and then set up the target link card.
+                               this.adapt( sourceTitle, mw.cx.targetLanguage )
+                                       .done( function ( adaptations ) {
+                                               if ( adaptations[ sourceTitle ] 
) {
+                                                       targetTitle = 
adaptations[ sourceTitle ];
+                                                       
linkCard.prepareTargetLinkCard(
+                                                               targetTitle,
+                                                               
mw.cx.targetLanguage,
+                                                               linkId
+                                                       );
+                                               }
+
+                                       } );
+                       }
+               } else {
+                       // If the click was on a target link or the target 
title came
+                       // from text selection or search, set up target card.
+                       this.prepareTargetLinkCard(
+                               targetTitle,
+                               mw.cx.targetLanguage,
+                               linkId
+                       );
                }
        };
 
@@ -650,9 +889,9 @@
                        this.$link.addClass( 'cx-highlight--blue' );
 
                        if ( this.isSourceLink() ) {
-                               $connectedLink = this.getTargetLink();
+                               $connectedLink = this.getTargetLink( 
this.$link.attr( 'data-linkid' ) );
                        } else {
-                               $connectedLink = this.getSourceLink();
+                               $connectedLink = this.getSourceLink( 
this.$link.attr( 'data-linkid' ) );
                        }
 
                        // Both methods above can return null, so we need to 
check if we have a link
@@ -717,6 +956,34 @@
                return false;
        }
 
+       /**
+        * Click handler for the red link markers in translation column.
+        */
+       function markerClickHandler() {
+               /*jshint validthis:true */
+               var $marker = $( this );
+
+               mw.hook( 'mw.cx.select.link' ).fire( $marker );
+
+               return false;
+       }
+
+       function highlightRedLinks() {
+               /*jshint validthis:true */
+               var $section = $( this );
+
+               $section.find( '.cx-redlink-marker' )
+                       .addClass( 'cx-redlink-marker-highlight' );
+       }
+
+       function removeRedLinkHighlights() {
+               /*jshint validthis:true */
+               var $section = $( this );
+
+               $section.find( '.cx-redlink-marker' )
+                       .removeClass( 'cx-redlink-marker-highlight' );
+       }
+
        function adaptLinks( $section ) {
                // WHYYYYYYY?
                // @todo refactor to avoid global reference
@@ -726,6 +993,10 @@
 
                // Handle clicks on the section, including future links
                $section.on( 'click', 'a', linkClickHandler );
+
+               // Set up handlers for redlink highlights
+               $section.on( 'focus', highlightRedLinks );
+               $section.on( 'blur', removeRedLinkHighlights );
 
                if ( $section.data( 'cx-draft' ) === true ) {
                        // This section is restored from draft. No need of link 
adaptation.
@@ -744,7 +1015,8 @@
                // updates the href appropriate for target language or removes 
the link.
                function apply( adaptations ) {
                        $links.map( function () {
-                               var $link = $( this ),
+                               var $marker,
+                                       $link = $( this ),
                                        href = $link.attr( 'href' );
 
                                // Identify this link as an adapted link in 
translation
@@ -752,10 +1024,17 @@
 
                                href = cleanupLinkHref( href );
                                if ( adaptations[ href ] ) {
-                                       $link.prop( 'href', adaptations[ href ] 
);
+                                       $link
+                                               .prop( 'href', adaptations[ 
href ] )
+                                               .attr( 'title', adaptations[ 
href ] );
                                } else {
-                                       // Remove the link
-                                       $link.after( $( this ).text() 
).remove();
+                                       $marker = $( '<span>' )
+                                               .addClass( 'cx-redlink-marker 
cx-redlink-marker-highlight' )
+                                               .attr( 'data-linkid', 
$link.attr( 'data-linkid' ) )
+                                               .html( $link.html() )
+                                               .click( markerClickHandler );
+
+                                       $link.replaceWith( $marker );
                                }
                        } );
                }
diff --git a/modules/tools/styles/ext.cx.tools.link.less 
b/modules/tools/styles/ext.cx.tools.link.less
index d9fceae..cc8f037 100644
--- a/modules/tools/styles/ext.cx.tools.link.less
+++ b/modules/tools/styles/ext.cx.tools.link.less
@@ -56,7 +56,7 @@
        border-top: 1px solid #dddddd;
 }
 
-.card__add-link {
+.card__add-link, .card__mark-link {
        @vertical-margin: 10px;
        @horizontal-margin: 15px;
        .mw-ui-item;
@@ -118,6 +118,12 @@
                        white-space: nowrap;
                }
        }
+
+       .card__missing-link-message {
+               margin: 0;
+               padding: 0;
+               font-size: 1em;
+       }
 }
 
 .card__link-instruction {
@@ -134,3 +140,14 @@
                color: #aaaaaa;
        }
 }
+
+.cx-redlink-marker-highlight {
+       color: #ccc;
+       border-bottom: 1px dashed #ccc;
+       cursor:pointer;
+}
+
+.cx-red-link {
+       color: red;
+}
+

-- 
To view, visit https://gerrit.wikimedia.org/r/181374
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6bce4bdd4688a5fb20c3268561329b126e57c3c7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Jsahleen <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to