client/general: add empty href for link buttons

In e464e69 I removed href='#' but I noticed that it broke some things.
Readding href serves two purposes:

- it makes links reachable with Tab key
- it makes links clickable with Enter key

The alternative to this approach was to introduce [tabindex] and [role]
attributes. But not only using tabindex=0 with <a/> is questionable,
it'd require adding a keyboard handler that'd intercept space and return
key presses and simulated link clicks. Since it's best to leave this
kind of thing to the native UI, I went with readding hrefs instead. I
believe that hash hrefs, even though being a common practice, are silly,
so I decided to settle down with empty hrefs.

As a bonus, I added a snippet that prevents middle mouse clicks from
opening such links/buttons in new tabs, which was the motivation for
e464e69.
This commit is contained in:
rr- 2016-08-22 01:25:10 +02:00
parent 44b2d9b830
commit d5e197e6ea
18 changed files with 47 additions and 28 deletions

View file

@ -32,13 +32,13 @@
--><span class='score-container'></span><!-- --><span class='score-container'></span><!--
--><% if (ctx.canEditComment) { %><!-- --><% if (ctx.canEditComment) { %><!--
--><a class='edit'><!-- --><a href class='edit'><!--
--><i class='fa fa-pencil'></i> edit<!-- --><i class='fa fa-pencil'></i> edit<!--
--></a><!-- --></a><!--
--><% } %><!-- --><% } %><!--
--><% if (ctx.canDeleteComment) { %><!-- --><% if (ctx.canDeleteComment) { %><!--
--><a class='delete'><!-- --><a href class='delete'><!--
--><i class='fa fa-remove'></i> delete<!-- --><i class='fa fa-remove'></i> delete<!--
--></a><!-- --></a><!--
--><% } %><!-- --><% } %><!--

View file

@ -16,8 +16,8 @@
<nav class='buttons'> <nav class='buttons'>
<ul> <ul>
<li class='preview'><a>Preview</a></li> <li class='preview'><a href>Preview</a></li>
<li class='edit'><a>Edit</a></li> <li class='edit'><a href>Edit</a></li>
</ul> </ul>
</nav> </nav>

View file

@ -1,6 +1,6 @@
<section class='expander'> <section class='expander'>
<header> <header>
<a> <a href>
<%- ctx.title %> <%- ctx.title %>
<i class='fa fa-chevron-down'></i> <i class='fa fa-chevron-down'></i>
</a> </a>

View file

@ -1,9 +1,9 @@
<% if (ctx.canFavorite) { %> <% if (ctx.canFavorite) { %>
<% if (ctx.ownFavorite) { %> <% if (ctx.ownFavorite) { %>
<a class='remove-favorite'> <a href class='remove-favorite'>
<i class='fa fa-heart'></i> <i class='fa fa-heart'></i>
<% } else { %> <% } else { %>
<a class='add-favorite'> <a href class='add-favorite'>
<i class='fa fa-heart-o'></i> <i class='fa fa-heart-o'></i>
<% } %> <% } %>
<% } else { %> <% } else { %>

View file

@ -57,9 +57,9 @@
<% if (ctx.canEditPostNotes) { %> <% if (ctx.canEditPostNotes) { %>
<section class='notes'> <section class='notes'>
<a class='add'>Add a note</a> <a href class='add'>Add a note</a>
<%= ctx.makeTextarea({disabled: true, text: 'Content (supports Markdown)', rows: '8'}) %> <%= ctx.makeTextarea({disabled: true, text: 'Content (supports Markdown)', rows: '8'}) %>
<a class='delete inactive'>Delete selected note</a> <a href class='delete inactive'>Delete selected note</a>
</section> </section>
<% } %> <% } %>
@ -74,7 +74,7 @@
<section class='post-thumbnail'> <section class='post-thumbnail'>
<label>Thumbnail</label> <label>Thumbnail</label>
<div class='dropper-container'></div> <div class='dropper-container'></div>
<a>Discard custom thumbnail</a> <a href>Discard custom thumbnail</a>
</section> </section>
<% } %> <% } %>
@ -82,10 +82,10 @@
<section class='management'> <section class='management'>
<ul> <ul>
<% if (ctx.canFeaturePosts) { %> <% if (ctx.canFeaturePosts) { %>
<li><a class='feature'>Feature this post on main page</a></li> <li><a href class='feature'>Feature this post on main page</a></li>
<% } %> <% } %>
<% if (ctx.canDeletePosts) { %> <% if (ctx.canDeletePosts) { %>
<li><a class='delete'>Delete this post</a></li> <li><a href class='delete'>Delete this post</a></li>
<% } %> <% } %>
</ul> </ul>
</section> </section>

View file

@ -26,10 +26,10 @@
</section> </section>
<section class='zoom'> <section class='zoom'>
<a class='fit-original'>Original zoom</a> &middot; <a href class='fit-original'>Original zoom</a> &middot;
<a class='fit-width'>fit width</a> &middot; <a href class='fit-width'>fit width</a> &middot;
<a class='fit-height'>height</a> &middot; <a href class='fit-height'>height</a> &middot;
<a class='fit-both'>both</a> <a href class='fit-both'>both</a>
</section> </section>
<section class='search'> <section class='search'>

View file

@ -1,5 +1,5 @@
<li class='uploadable'> <li class='uploadable'>
<a class='remove'> <a href class='remove'>
<i class='fa fa-remove'></i> <i class='fa fa-remove'></i>
</a> </a>

View file

@ -12,11 +12,11 @@
<% if (ctx.parameters.tag) { %> <% if (ctx.parameters.tag) { %>
<span class='append'>Tagging with:</span> <span class='append'>Tagging with:</span>
<% } else { %> <% } else { %>
<a class='mousetrap button append open-masstag'>Mass tag</a> <a href class='mousetrap button append open-masstag'>Mass tag</a>
<% } %> <% } %>
<%= ctx.makeTextInput({name: 'masstag', value: ctx.parameters.tag}) %> <%= ctx.makeTextInput({name: 'masstag', value: ctx.parameters.tag}) %>
<input class='mousetrap start-tagging' type='submit' value='Start tagging'/> <input class='mousetrap start-tagging' type='submit' value='Start tagging'/>
<a class='mousetrap button append stop-tagging'>Stop tagging</a> <a href class='mousetrap button append stop-tagging'>Stop tagging</a>
</form> </form>
<% } %> <% } %>
</div> </div>

View file

@ -36,7 +36,7 @@
<% } %> <% } %>
</a> </a>
<% if (ctx.parameters && ctx.parameters.tag) { %> <% if (ctx.parameters && ctx.parameters.tag) { %>
<a data-post-id='<%= post.id %>' class='masstag'> <a href data-post-id='<%= post.id %>' class='masstag'>
</a> </a>
<% } %> <% } %>
</li> </li>

View file

@ -1,5 +1,5 @@
<% if (ctx.canScore) { %> <% if (ctx.canScore) { %>
<a class='upvote'> <a href class='upvote'>
<% if (ctx.ownScore == 1) { %> <% if (ctx.ownScore == 1) { %>
<i class='fa fa-thumbs-up'></i> <i class='fa fa-thumbs-up'></i>
<% } else { %> <% } else { %>
@ -9,13 +9,13 @@
<span class='vim-nav-hint'>like</span> <span class='vim-nav-hint'>like</span>
</a> </a>
<% } else { %> <% } else { %>
<a class='upvote inactive'> <a href class='upvote inactive'>
<i class='fa fa-thumbs-o-up'></i> <i class='fa fa-thumbs-o-up'></i>
</a> </a>
<% } %> <% } %>
<span class='value'><%- ctx.score %></span> <span class='value'><%- ctx.score %></span>
<% if (ctx.canScore) { %> <% if (ctx.canScore) { %>
<a class='downvote'> <a href class='downvote'>
<% if (ctx.ownScore == -1) { %> <% if (ctx.ownScore == -1) { %>
<i class='fa fa-thumbs-down'></i> <i class='fa fa-thumbs-down'></i>
<% } else { %> <% } else { %>

View file

@ -14,7 +14,7 @@
</table> </table>
<% if (ctx.canCreate) { %> <% if (ctx.canCreate) { %>
<p><a class='add'>Add new category</a></p> <p><a href class='add'>Add new category</a></p>
<% } %> <% } %>
<div class='messages'></div> <div class='messages'></div>

View file

@ -31,13 +31,13 @@
<% if (ctx.tagCategory.tagCount) { %> <% if (ctx.tagCategory.tagCount) { %>
<a class='inactive' title="Can't delete category in use">Remove</a> <a class='inactive' title="Can't delete category in use">Remove</a>
<% } else { %> <% } else { %>
<a>Remove</a> <a href>Remove</a>
<% } %> <% } %>
</td> </td>
<% } %> <% } %>
<% if (ctx.canSetDefault) { %> <% if (ctx.canSetDefault) { %>
<td class='set-default'> <td class='set-default'>
<a>Make default</a> <a href>Make default</a>
</td> </td>
<% } %> <% } %>
</tr> </tr>

View file

@ -8,8 +8,8 @@
<div class='wrapper'> <div class='wrapper'>
<p> <p>
<span class='buttons'> <span class='buttons'>
<a class='opacity'><i class='fa fa-eye'></i></a> <a href class='opacity'><i class='fa fa-eye'></i></a>
<a class='close'>×</a> <a href class='close'>×</a>
</span> </span>
Suggested tags Suggested tags
</p> </p>

View file

@ -215,6 +215,7 @@ class AutoCompleteControl {
const listItem = document.createElement('li'); const listItem = document.createElement('li');
const link = document.createElement('a'); const link = document.createElement('a');
link.innerHTML = resultItem.caption; link.innerHTML = resultItem.caption;
link.setAtribute('href', '');
link.setAttribute('data-key', resultItem.value); link.setAttribute('data-key', resultItem.value);
link.addEventListener( link.addEventListener(
'mouseenter', 'mouseenter',

View file

@ -146,12 +146,14 @@ class PostEditSidebarControl extends events.EventTarget {
} }
_evtRemoveThumbnailClick(e) { _evtRemoveThumbnailClick(e) {
e.preventDefault();
this._thumbnailFileDropper.reset(); this._thumbnailFileDropper.reset();
this._newPostThumbnail = null; this._newPostThumbnail = null;
this._thumbnailRemovalLinkNode.style.display = 'none'; this._thumbnailRemovalLinkNode.style.display = 'none';
} }
_evtFeatureClick(e) { _evtFeatureClick(e) {
e.preventDefault();
if (confirm('Are you sure you want to feature this post?')) { if (confirm('Are you sure you want to feature this post?')) {
this.dispatchEvent(new CustomEvent('feature', { this.dispatchEvent(new CustomEvent('feature', {
detail: { detail: {
@ -162,6 +164,7 @@ class PostEditSidebarControl extends events.EventTarget {
} }
_evtDeleteClick(e) { _evtDeleteClick(e) {
e.preventDefault();
if (confirm('Are you sure you want to delete this post?')) { if (confirm('Are you sure you want to delete this post?')) {
this.dispatchEvent(new CustomEvent('delete', { this.dispatchEvent(new CustomEvent('delete', {
detail: { detail: {
@ -195,6 +198,7 @@ class PostEditSidebarControl extends events.EventTarget {
} }
_evtAddNoteClick(e) { _evtAddNoteClick(e) {
e.preventDefault();
if (e.target.classList.contains('inactive')) { if (e.target.classList.contains('inactive')) {
return; return;
} }
@ -203,6 +207,7 @@ class PostEditSidebarControl extends events.EventTarget {
} }
_evtDeleteNoteClick(e) { _evtDeleteNoteClick(e) {
e.preventDefault();
if (e.target.classList.contains('inactive')) { if (e.target.classList.contains('inactive')) {
return; return;
} }

View file

@ -251,6 +251,7 @@ class TagInputControl extends events.EventTarget {
} }
_evtAddTagButtonClick(e) { _evtAddTagButtonClick(e) {
e.preventDefault();
this.addTag(this._tagInputNode.value, SOURCE_USER_INPUT); this.addTag(this._tagInputNode.value, SOURCE_USER_INPUT);
this._tagInputNode.value = ''; this._tagInputNode.value = '';
} }
@ -324,6 +325,7 @@ class TagInputControl extends events.EventTarget {
const removalLinkNode = document.createElement('a'); const removalLinkNode = document.createElement('a');
removalLinkNode.classList.add('append'); removalLinkNode.classList.add('append');
removalLinkNode.setAttribute('href', '')
removalLinkNode.setAttribute('data-pseudo-content', '×'); removalLinkNode.setAttribute('data-pseudo-content', '×');
removalLinkNode.addEventListener('click', e => { removalLinkNode.addEventListener('click', e => {
e.preventDefault(); e.preventDefault();
@ -387,6 +389,7 @@ class TagInputControl extends events.EventTarget {
const addLinkNode = document.createElement('a'); const addLinkNode = document.createElement('a');
addLinkNode.textContent = tagName; addLinkNode.textContent = tagName;
addLinkNode.classList.add('add-tag'); addLinkNode.classList.add('add-tag');
addLinkNode.setAttribute('href', '');
if (actualTag) { if (actualTag) {
addLinkNode.classList.add( addLinkNode.classList.add(
misc.makeCssName(actualTag.category, 'tag')); misc.makeCssName(actualTag.category, 'tag'));
@ -405,6 +408,7 @@ class TagInputControl extends events.EventTarget {
const removeLinkNode = document.createElement('a'); const removeLinkNode = document.createElement('a');
removeLinkNode.classList.add('remove-tag'); removeLinkNode.classList.add('remove-tag');
removeLinkNode.classList.add('append'); removeLinkNode.classList.add('append');
removeLinkNode.setAttribute('href', '');
removeLinkNode.setAttribute('data-pseudo-content', '×'); removeLinkNode.setAttribute('data-pseudo-content', '×');
removeLinkNode.addEventListener('click', e => { removeLinkNode.addEventListener('click', e => {
e.preventDefault(); e.preventDefault();

View file

@ -476,6 +476,14 @@ document.addEventListener('input', e => {
} }
}); });
// prevent opening buttons in new tabs
document.addEventListener('click', e => {
if (e.target.getAttribute('href') === '' && e.which === 2) {
console.log('prevented');
e.preventDefault();
}
});
module.exports = misc.arrayToObject([ module.exports = misc.arrayToObject([
htmlToDom, htmlToDom,
getTemplate, getTemplate,

View file

@ -206,6 +206,7 @@ class PostUploadView extends events.EventTarget {
} }
_evtRemoveClick(e, uploadable) { _evtRemoveClick(e, uploadable) {
e.preventDefault();
this.removeUploadable(uploadable); this.removeUploadable(uploadable);
} }