Changeset - 422671dd32df
[Not reviewed]
default
0 3 0
domruf - 8 years ago 2017-12-16 22:10:45
dominikruf@gmail.com
css: use pseudo-content trick to prevent diff line numbers from being pasted to text

When copy-pasting a diff from Chrome to a text editor, line numbers (on
separate lines) would be pasted as well. Even though 'user-select: none'
prevents text from being visually selected, in Chrome, the text still gets
copied to the clipboard when the user for example presses ctrl-c. (It worked in
Firefox.)

Instead, don't put the line numbers directly in the DOM, but put them in a data
attribute and render them as :before. That will give the same rendering as
before but prevent it from being copied.

(Firefox will however still add empty lines - that is how <pre> is hardcoded to
be rendered when pasting to text.)
3 files changed with 6 insertions and 3 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/diffs.py
Show inline comments
 
@@ -30,97 +30,97 @@ import difflib
 
import logging
 

	
 
from tg.i18n import ugettext as _
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.vcs.exceptions import VCSError
 
from kallithea.lib.vcs.nodes import FileNode, SubModuleNode
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.utils2 import safe_unicode
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _safe_id(idstring):
 
    """Make a string safe for including in an id attribute.
 

	
 
    The HTML spec says that id attributes 'must begin with
 
    a letter ([A-Za-z]) and may be followed by any number
 
    of letters, digits ([0-9]), hyphens ("-"), underscores
 
    ("_"), colons (":"), and periods (".")'. These regexps
 
    are slightly over-zealous, in that they remove colons
 
    and periods unnecessarily.
 

	
 
    Whitespace is transformed into underscores, and then
 
    anything which is not a hyphen or a character that
 
    matches \w (alphanumerics and underscore) is removed.
 

	
 
    """
 
    # Transform all whitespace to underscore
 
    idstring = re.sub(r'\s', "_", idstring)
 
    # Remove everything that is not a hyphen or a member of \w
 
    idstring = re.sub(r'(?!-)\W', "", idstring).lower()
 
    return idstring
 

	
 

	
 
def as_html(table_class='code-difftable', line_class='line',
 
            old_lineno_class='lineno old', new_lineno_class='lineno new',
 
            no_lineno_class='lineno',
 
            code_class='code', enable_comments=False, parsed_lines=None):
 
    """
 
    Return given diff as html table with customized css classes
 
    """
 
    def _link_to_if(condition, label, url):
 
        """
 
        Generates a link if condition is meet or just the label if not.
 
        """
 

	
 
        if condition:
 
            return '''<a href="%(url)s">%(label)s</a>''' % {
 
            return '''<a href="%(url)s" data-pseudo-content="%(label)s"></a>''' % {
 
                'url': url,
 
                'label': label
 
            }
 
        else:
 
            return label
 

	
 
    _html_empty = True
 
    _html = []
 
    _html.append('''<table class="%(table_class)s">\n''' % {
 
        'table_class': table_class
 
    })
 

	
 
    for diff in parsed_lines:
 
        for line in diff['chunks']:
 
            _html_empty = False
 
            for change in line:
 
                _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
 
                    'lc': line_class,
 
                    'action': change['action']
 
                })
 
                anchor_old_id = ''
 
                anchor_new_id = ''
 
                anchor_old = "%(filename)s_o%(oldline_no)s" % {
 
                    'filename': _safe_id(diff['filename']),
 
                    'oldline_no': change['old_lineno']
 
                }
 
                anchor_new = "%(filename)s_n%(oldline_no)s" % {
 
                    'filename': _safe_id(diff['filename']),
 
                    'oldline_no': change['new_lineno']
 
                }
 
                cond_old = (change['old_lineno'] != '...' and
 
                            change['old_lineno'])
 
                cond_new = (change['new_lineno'] != '...' and
 
                            change['new_lineno'])
 
                no_lineno = (change['old_lineno'] == '...' and
 
                             change['new_lineno'] == '...')
 
                if cond_old:
 
                    anchor_old_id = 'id="%s"' % anchor_old
 
                if cond_new:
 
                    anchor_new_id = 'id="%s"' % anchor_new
 
                ###########################################################
 
                # OLD LINE NUMBER
 
                ###########################################################
 
                _html.append('''\t<td %(a_id)s class="%(olc)s" %(colspan)s>''' % {
 
                    'a_id': anchor_old_id,
 
                    'olc': no_lineno_class if no_lineno else old_lineno_class,
 
                    'colspan': 'colspan="2"' if no_lineno else ''
 
                })
kallithea/public/less/kallithea-diff.less
Show inline comments
 
@@ -74,98 +74,96 @@ BIN_FILENODE = 6
 

	
 
  /* line coloring */
 
  .context {
 
    background: none repeat scroll 0 0 #DDE7EF;
 
    color: #999;
 
  }
 
  .add {
 
    background: none repeat scroll 0 0 #DDFFDD;
 
  }
 
  .add ins {
 
    background: none repeat scroll 0 0 #AAFFAA;
 
    text-decoration: none;
 
  }
 
  .del {
 
    background: none repeat scroll 0 0 #FFDDDD;
 
  }
 
  .del del {
 
    background: none repeat scroll 0 0 #FFAAAA;
 
    text-decoration: none;
 
  }
 

	
 
  /* tabs */
 
  td.code pre u:before {
 
    content: "\21a6";
 
    display: inline-block;
 
    width: 0;
 
  }
 
  /* CR */
 
  td.code pre u.cr:before {
 
    content: "\21a4";
 
    display: inline-block;
 
  }
 
  /* whitespace characters */
 
  td.code pre u {
 
    color: rgba(0, 0, 0, 0.3);
 
  }
 
  /* trailing spaces */
 
  td.code pre i {
 
    border-style: solid;
 
    border-width: 0 0 0 1px;
 
    border-color: rgba(0, 0, 0, 0.3);
 
  }
 

	
 
  /** LINE NUMBERS **/
 
  .lineno {
 
    padding-left: 2px;
 
    padding-right: 2px !important;
 
    width: 30px;
 
    -moz-user-select: none;
 
    -webkit-user-select: none;
 
    border-right: 1px solid #CCC !important;
 
    border-left: 0px solid #CCC !important;
 
    border-top: 0px solid #CCC !important;
 
    border-bottom: none !important;
 
    vertical-align: middle !important;
 
    text-align: center;
 
  }
 
  .lineno.new {
 
    text-align: right;
 
  }
 
  .lineno.old {
 
    text-align: right;
 
  }
 
  .lineno a {
 
    color: #aaa !important;
 
    font-size: 11px;
 
    font-family: @font-family-monospace;
 
    line-height: normal;
 
    padding-left: 6px;
 
    padding-right: 6px;
 
    display: block;
 
  }
 
  .line:hover .lineno a {
 
    color: #333 !important;
 
  }
 
  /** CODE **/
 
  .code {
 
    display: block;
 
  }
 
  .code pre {
 
    border: 0;
 
    padding: 0;
 
    margin: 0;
 
    background: none;
 
    min-height: 17px;
 
    line-height: 17px;
 
    white-space: pre-wrap;
 
    word-break: break-all;
 
  }
 

	
 
  /* leading +/- on changed lines */
 
  .del .code pre:before {
 
    content: "-";
 
    color: #800;
 
  }
 
  .add .code pre:before {
 
    content: "+";
 
    color: #080;
kallithea/public/less/style.less
Show inline comments
 
body {
 
  background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
 
}
 

	
 
/* pseude content that should not be selected or copied by the user */
 
[data-pseudo-content]:before {
 
  content: attr(data-pseudo-content);
 
}
 

	
 
/* class for texts where newlines should be preserved, for very light-weight ascii art markup (like pull request descriptions) */
 
.formatted-fixed {
 
  white-space: pre-wrap;
 
}
 

	
 
/* use monospace for changeset hashes */
 
.changeset_hash {
 
  font-family: @font-family-monospace;
 
}
 

	
 
/* Note: class 'icon-empty' or 'icon-gravatar' can be used to get icon-ish styling without an actual glyph */
 
i[class^='icon-empty'],
 
i[class^='icon-gravatar'] {
 
  background-repeat: no-repeat;
 
  background-position: center;
 
  display: inline-block;
 
  min-width: 16px;
 
  min-height: 16px;
 
  margin: -2px 0 -4px 0;
 
}
 

	
 
.inline-comments-general.show-general-status .hidden.general-only {
 
  display: block !important;
 
}
 
.truncate {
 
  white-space: nowrap;
 
  overflow: hidden;
 
  text-overflow: ellipsis;
 
  -o-text-overflow: ellipsis;
 
  -ms-text-overflow: ellipsis;
 
}
 
.truncate.autoexpand:hover {
 
  overflow: visible;
 
}
 

	
 
/* show comment anchors when hovering over panel-heading */
 
a.permalink {
 
  visibility: hidden;
 
}
 
.panel-heading:hover .permalink {
 
  visibility: visible;
 
}
 

	
 
.navbar-inverse {
 
  border: none;
 
}
 

	
 
/* logo */
0 comments (0 inline, 0 general)