Unverified Commit 0de2c7e5 authored by Stefan Auditor's avatar Stefan Auditor Committed by Stefan Auditor
Browse files

Issue #3282696 by sanduhrs: Port of l10n_community: Add editor library

parent beaa6727
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line

li.translation {
  direction: rtl;
  padding: 4px 20px 4px 0;
  clear: right;
}

li.translation > .selector {
  left: auto;
  right: 2px;
}

.l10n-table ul.actions {
  float: left;
}
  .l10n-table ul.actions {
    float: left;
    margin-right:0;
    margin-left:4px;
  }
  .l10n-table ul.actions li {
    float: left;
    padding: 0 4px 4px 0;
  }
  .l10n-table ul.actions li.declined label {
    background: url(images/decline.png) no-repeat left top;
  }
  .l10n-table ul.actions li.edit label {
    background: url(images/edit-rtl.png) no-repeat left top;
  }
  li.translation.is-declined > .actions li.declined label {
    background-image:url(images/undecline-rtl.png);
  }

li.translation > .author {
  padding-left: 0;
  padding-right: 10px;
}
  li.new-translation .l10n-string {
    margin-right: 0;
    margin-left: 6px;
  }
  li.new-translation textarea {
    clear: right;
  }
  li.filter-match, label.filter-match {
    border-width: 0 0 1px 1px;
  }
 No newline at end of file
+330 −0
Original line number Diff line number Diff line

body {
  font-family: Verdana;
  font-size: 12px;
}


#l10n-community-translate-form .sticky-header {
  z-index: 2;
}

.l10n-table .form-item {
  margin: 0;
  padding: 0;
}

.l10n-table {
  margin: 1em 0;
}
.l10n-table td {
  vertical-align: top;
  padding: 0.5em;
}

.l10n-table td ul {
  margin: 0;
  padding: 0;
}
.l10n-table td li {
  list-style: none;
  background: none;
  padding: 0;
  margin: 0;
}

.l10n-table label {
  font-weight: normal;
}

td.source {
  width: 45%;
  direction: ltr;
}
  td.source .l10n-sid {
    padding-right: 6px;
    font-size: 10px;
    color: #000;
    float: right;
    clear: both;
  }

  td.source .l10n-usage {
    padding: 0 6px 6px 16px;
    font-size: 10px;
    line-height: 12px;
  }
  td.source .l10n-usage .l10n-more-info {
    padding-left: 16px;
  }
  td.source .l10n-usage .l10n-more-link {
    font-size: 10px;
    text-decoration: none;
    color: #000;
    opacity: 0.4;
  }
  td.translation .l10n-byline .l10n-more-link:before,
  td.source .l10n-usage .l10n-more-link:before {
    content: "▾ ";
  }
  td.source .l10n-usage .l10n-more-link:hover,
  td.source .l10n-usage .l10n-more-link:active {
    color: #666;
  }
  td.source .l10n-usage li {
    list-style-type:disc;
    background: none;
    margin: 0 0 0 22px;
    padding: 0;
  }

td li.translation {
  padding: 4px 0 4px 20px; /* LTR */
  clear: left; /* LTR */
  position: relative;
  -webkit-transition: opacity 0.2s ease-out;
}

td li.translation > .selector {
  position: absolute;
  left: 2px; /* LTR */
  top: 5px;
  margin: 0;
}


.l10n-table ul.actions {
  float: right; /* LTR */
  margin: 0;
}
  li.translation.is-active > .actions li.declined { display: none; }
  .l10n-table ul.actions {
    overflow: hidden;
    float: right; /* LTR */
    margin-right: 4px; /* LTR */
    -webkit-user-select: none;
  }
  .l10n-table ul.actions li {
    float: right; /* LTR */
    font-size: 80%;
    line-height: 20px;
    padding: 0 0 4px 4px; /* LTR */
  }
  .l10n-table ul.actions li label {
    width: 16px;
    height: 16px;
    overflow: hidden;
    display: block;
    text-indent: -1000px;
    opacity: 0.6;
    cursor: pointer;
  }
  .l10n-table ul.actions li label:active {
    position: relative;
    top: 1px;
  }
  .l10n-table ul.actions li.declined label {
    background: url(images/decline.png) no-repeat right top; /* LTR */
  }
  .l10n-table ul.actions li.edit label {
    background: url(images/edit.png) no-repeat right top; /* LTR */
  }
  li.translation.is-declined > .actions li.declined label {
    background-image: url(images/undecline.png); /* LTR */
  }
  li.translation:hover > .actions li label {
    opacity: 1;
  }
  .actions li.declined .form-checkbox {
    display: none;
  }

  .l10n-string > br {
    display: block;
    content: "";
    height: 1px;
    background: #CCC;
    margin: 2px 0;
  }

li.translation .l10n-string,
td.source .l10n-string,
td.sid a {
  display: block;
  line-height: 16px;
}
  li.translation .l10n-string > span,
  td.source .l10n-string > span {
    white-space: pre-wrap;
    -webkit-transition: opacity 0.2s ease-out;
  }
  li.translation.is-declined .l10n-string {
    color: #999;
    height: 16px;
    overflow: hidden;
    text-decoration: line-through;
  }
  li.translation.no-translation .l10n-string span {
    font-style: italic;
    color: #666;
  }


li.translation > .author {
  font-size: 10px;
  padding-left: 10px; /* LTR */
  line-height: 12px;
  color: #999;
}
  li.translation.is-declined > .author {
    display: none;
  }
  li.translation > .author a {
    color: #666;
  }


  li.new-translation:not(.focused):not(.has-content):not(.is-active) > .l10n-string {
    display: none;
  }
  li.new-translation.has-content > .actions {
      display: block;
    }
    li.new-translation .l10n-string {
      display: block;
      margin-bottom: 4px;
      margin-right: 6px; /* LTR */
    }
    li.new-translation .l10n-string > span:after {
      content: "*";
      color: #E09010;
    }
    li.new-translation .l10n-string > span:empty:after {
      content: attr(data-empty);
      font-style: italic;
      color: #666;
    }
  li.new-translation > .form-item ~ .form-item {
    margin-top: 5px;
  }
  li.new-translation:not(.has-content):not(.focused) > .form-item ~ .form-item {
    display: none;
  }
  li.new-translation textarea {
    height: 60px;
    clear: left; /* LTR */
    width: 100%;
    padding: 0;
    margin: 0;
    font-size: 100%;
    font-family: inherit;
    -webkit-transition: height 0.2s ease-out;
  }
  li.new-translation:not(.focused) textarea:not(:focus):hover {
    opacity: 0.75;
  }
  li.new-translation:not(.focused) .grippie {
    visibility: hidden;
    margin-bottom: -9px;
  }
  li.new-translation textarea:not(:focus) {
    height: 19px !important;
    overflow: hidden;
    resize: none;
  }
  li.new-translation:not(.focused) textarea {
    opacity: 0.5 !important;
  }
  li.new-translation.focused textarea {
    opacity: 1;
    position: relative;
    z-index: 1;
  }
  li.filter-match, label.filter-match {
    -moz-border-radius: 4px;
    -webkit-border-radius: 3px;
    border-color: #cdcdcd;
    border-style: solid;
    border-width: 0 1px 1px 0; /* LTR */
    margin: 0 0 5px;
  }





span.l10n-escape,
span.l10n-nl::after,
em.l10n-placeholder,
code
 {
  font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
  font-size: 12px;
  line-height: 14px;
}




.l10n-string span.l10n-nl::after {
  content: "¬";
  opacity: 0.4;
  line-height: 19px;
  vertical-align: top;
}


.l10n-string code,
.l10n-string em.l10n-placeholder {
  background: transparent;
  padding: 0 1px;
  margin: 0 -1px;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border: 1px solid transparent;
  font-style: normal;
}

tr:hover .l10n-string code,
tr:hover .l10n-string em.l10n-placeholder {
  background: rgba(0,0,0,0.05);
}

tr:hover .l10n-string em.l10n-placeholder {
}

tr:hover .l10n-string em.l10n-placeholder.highlight {
border-color: rgba(0,0,0,0.2);
  background: rgba(0,0,0,0.1);
}

.l10n-string code em.l10n-placeholder {
  top: 0;
}

.l10n-string span.worddiff-del del,
.l10n-string span.worddiff-ins ins {
  text-decoration: none;
  border: 1px solid transparent;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  padding: 0;
  margin: 0 -1px;
}

.l10n-string > span.worddiff-del { background: #FFD8D8; }
.l10n-string > span.worddiff-del del { background: #FF8888; }
.l10n-string > span.worddiff-ins { background: #DDF8CC; }
.l10n-string > span.worddiff-ins ins { background: #90F678; }

.clearfix:after {
  content: ".";
  display: block;
  height: 0;
  clear: both;
  visibility: hidden;
  }

html.js .js-hide {
  display: none;
}
+270 −0
Original line number Diff line number Diff line

/**
 * @file
 *   Translation editor behaviors.
 */

(function($) {

  /**
   * Simple string encoding/escaping for proper HTML output.
   */
  encode = function(str) {
    str = String(str);
    var replace = { '&': '&amp;', '<': '&lt;', '>': '&gt;' };
    for (var character in replace) {
      var regex = new RegExp(character, 'g');
      str = str.replace(regex, replace[character]);
    }
    return str;
  };

  // Add behaviors to placeholders so that they highlight the corrsponding
  // placeholder(s) with the same name on the same table row.
  // @todo https://stackoverflow.com/questions/14354040/jquery-1-9-live-is-not-a-function
  // $('em.l10n-placeholder')
  //   .live('mouseover', function() {
  //     $(this).closest('tr').find('.l10n-placeholder:contains("' + $(this).text() + '")').addClass('highlight');
  //   })
  //   .live('mouseout', function() {
  //     $('.l10n-placeholder.highlight').removeClass('highlight');
  //   });

  $(function () {

    // Replace "More information" link with AJAX output.
    $('.l10n-usage .l10n-more-link').click(function() {
      if ($(this).siblings('.l10n-more-info').css('display') == 'none') {
        // Was shown before but is currently hidden.
        $(this).html(Drupal.t('Hide related projects')).siblings('.l10n-more-info').toggle();
      }
      else if ($(this).siblings('.l10n-more-info').html()) {
        // Is shown and needs to be hidden.
        $(this).html(Drupal.t('Show related projects')).siblings('.l10n-more-info').toggle();
      }
      else {
        // Was not yet loaded, we want to load the information fresh from the server.
        // Append /1 to the href, telling the server we want AHAH targeted output.
        $(this).html(Drupal.t('Loading...')).siblings('.l10n-more-info').load(this.href + '/1', function(){$(this).siblings('.l10n-more-link').html(Drupal.t('Hide related projects'));});
      }
      // Prevent the actual link click from happening.
      return false;
    });

    // Provide more history about string submissions.
    $('.l10n-byline .l10n-more-link').click(function() {
      if (!$(this).siblings('.l10n-more-info').html()) {
        // Was not yet loaded, we want to load the information fresh from the server.
        // Append /1 to the href, telling the server we want AHAH targeted output.
        $(this).html(Drupal.t('Loading...')).siblings('.l10n-more-info').load(this.href + '/1', function(){$(this).siblings('.l10n-more-link').hide();});
      }
      // Prevent the actual link click from happening.
      return false;
    });

    // Callback to show all filters.
    var showAllFilters = function() {
      $('#l10n-community-filter-form .reveal-link').hide();
      $('#l10n-community-filter-form .filter-widget:hidden').removeClass('js-hide').fadeIn();
      return false;
    }

    // If we did hide items, add a link to show all.
    if ($('#l10n-community-filter-form .filter-widget:hidden')) {
      $('#l10n-community-filter-form').append('<a href="#" class="reveal-link">' + Drupal.t('Reveal more filters') + '</a>');
      $('#l10n-community-filter-form .reveal-link').click(showAllFilters);
    }

     // Add title to all decline buttons. Will be modified dynamically.
     $('.actions .declined label').attr('title', Drupal.t('Decline'));

    var markup = function(string) {
      // Highlight placeholders with the l10n-placeholder class.
      string = string.replace(/([!@%]|<(ins|del)>[!@%]<\/(ins|del)>)([\w-]+|<(ins|del)>[\w-]+<\/(ins|del)>)/g, '<em class="l10n-placeholder">$&</em>');

      // Wrap HTML tags in <code> tags.
      string = string.replace(/(&lt;.+?(&gt;|$))/g, function(str) {
        return '<code>' + str.replace(/<[^>]+>/g, '</code>$&<code>') + '</code>';
      });

      string = string.replace(/\\[^<]/g, '<span class="l10n-escape">$&</span>');

      // Add markers for newlines.
      string = string.replace(/\n/g, '<span class="l10n-nl"></span>$&');

      return string;
    };

    $('td.translation').parent().each(function() {
      var all = $('li.translation', this);
      var strings = all.find('.l10n-string > span');
      var source = $('td.source', this);

      // Add special tags to the source markup cells.
      source.find('.l10n-string span').each(function() {
        $(this).html(markup($(this).html()));
      });

      // Initialize data for the worddiff tool.
      strings.each(function() {
        var orig = $(this).html(), markedUp = markup(orig);
        $(this)
          .html(markedUp)
          .data('worddiff:original', orig)
          .data('worddiff:markup', markedUp);
      });

      // Method to set status classes based on associated checkbox value.
      var setStatus = function(elem, status, value) {
        newValue = elem.find('.' + status + ' :checkbox').attr('checked', value).attr('checked');
        elem[(newValue === undefined ? value : newValue) ? 'addClass' : 'removeClass']('is-' + status);
        if (status == 'declined') {
          elem.find('li.declined label').attr('title', value ? Drupal.t('Undo decline') : Drupal.t('Decline'));
        }
      };

      var textareas = all.filter('.new-translation').find('textarea');

      // Callback for when the edit button was pressed.
      $(this).find('ul.actions .edit').click(function() {
        var translation = $(this).closest('td.source, li.translation');
        var confirmed = undefined;
        textareas.each(function(i) {
          var textarea = $(this);
          var val = textarea.val();
          if (confirmed || val === textarea.attr('defaultValue') || !val || (confirmed === undefined && (confirmed = confirm(Drupal.t("Do you want to overwrite the current suggestion?"))))) {
            // If not the default value, and still editing that means there was something
            // added into the field without it being saved first, and is being edited again.
            textarea.val(translation.find('.l10n-string > span:eq('+ i +')').text()).keyup();
            if (i == 0) {
              // Since we can't have multiple focuses, we jut focus the first textarea.
              textarea.focus();
            }
          }
        });
      });

      all.each(function() {
        var translation = $(this);
        var isTranslation = !translation.is('.no-translation');
        var siblings = all.not(this).not('.no-translation');

        var removeDiff = function() {
          strings.worddiffRevert();
        };

        var updateDiff = function() {
          removeDiff();
          if (isTranslation) {
            var orig = siblings.filter('.is-active');
            if (!orig.length) {
              orig = siblings.filter('.default');
            }
            if (!orig.length) {
              orig = all.not('.no-translation').eq(0).not(translation);
            }
            if (orig.length) {
              orig = orig.find('.l10n-string > span');
              translation.find('.l10n-string > span').each(function(i) {
                $(this).worddiff(orig.get(i), markup);
              });
            }
          }
        };

        translation.find('> .selector').click(function() {
          // Set this undeclined.
          setStatus(translation, 'declined', false);
          // Mark the previously active translation declined, if that is possible.
          setStatus(translation.siblings('.is-active.is-declinable:not(.new-translation)'), 'declined', true);
          // Move active mark to this one.
          setStatus(translation.siblings('.is-active'), 'active', false);
          translation.addClass('is-active');
        });

        // Update decline status based on checkbox values.
        translation.find('> .actions .declined :checkbox').change(function() {
          setStatus(translation, 'declined', this.checked);
        });

        if (isTranslation) {
          // Add doubleclick behavior to decline all other suggestions.
          translation.filter('.is-selectable').find('.l10n-string').dblclick(function() {
            translation.siblings('.is-declinable').each(function () {
              setStatus($(this), 'declined', true);
            });
          });

          // Add hover behavior to update and remove diffs.
          translation
            .mouseenter(updateDiff)
            .mouseleave(removeDiff);
        }

        if (translation.is('.new-translation')) {
          translation.find('> .selector').click(function() {
            textareas.each(function() {
              var textarea = $(this);
              if (textarea.val() === '' || textarea.val() === textarea.attr('defaultValue')) {
                textarea.focus();
                // Stop checking the other ones.
                return false;
              }
            });
          });

          // Does any of the textareas have any content?
          var hasContent = function() {
            for (var i = 0; i < textareas.length; i++) {
              if (textareas[i].value && textareas[i].value !== textareas[i].defaultValue) {
                return true;
              }
            }
            return false;
          };

          var blurTimeout;
          textareas.each(function(n) {
            var wrapper = $(this);
            var textarea = $(this);
            var text = translation.find('.l10n-string > span').eq(n);

            textarea
              .focus(function() {
                translation.addClass('focused');
                clearTimeout(blurTimeout);
                // Empty textarea when focused.
                if (textarea.val() === textarea.attr('defaultValue')) {
                  textarea.val('');
                }
              })
              .blur(function() {
                blurTimeout = setTimeout(function() {
                  translation.removeClass('focused');
                  // Add back default value if user moved out and kept the original text.
                  if (textarea.val() === '') {
                    textarea.val(textarea.attr('defaultValue'));
                  }
                  translation[hasContent() ? 'addClass' : 'removeClass']('has-content');
                }, 1000);
              })
              .keyup(function() {
                // Encode and compute the diff for the text as text is typed.
                var val = encode(textarea.val());
                text
                  .data('worddiff:original', val)
                  .data('worddiff:markup', markup(val));
                var oldPos = textarea.offset().top;
                updateDiff();
                var diff = textarea.offset().top - oldPos;
                if (diff) {
                  window.scrollBy(0, diff);
                }
              });
          });
        }
      });
    });
  });

})(jQuery);
+208 −0

File added.

Preview size limit exceeded, changes collapsed.

+16 −0
Original line number Diff line number Diff line
(function($, drupalSettings) {

  Drupal.behaviors.replaceL10nServerURLs = {
    attach: function(context) {
      // Act on links if we have URL replacements to do.
      if (drupalSettings.l10nServerURLs) {
        for (var url in drupalSettings.l10nServerURLs) {
          // Look for links with this exact URL and replace with extended version.
          // This ensures we keep filter values while switching tabs.
          $('a[href="' + url +'"]', context).not('.filter-exclude').attr('href', drupalSettings.l10nServerURLs[url]);
        }
      }
    }
  }

})(jQuery, drupalSettings);
Loading