mirror of https://github.com/keeweb/keeweb.git
custom tooltips
This commit is contained in:
parent
0977595712
commit
c26406940c
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Backbone = require('backbone'),
|
||||
Tip = require('../util/tip');
|
||||
|
||||
_.extend(Backbone.View.prototype, {
|
||||
hide: function() {
|
||||
|
@ -48,6 +49,7 @@ _.extend(Backbone.View.prototype, {
|
|||
this.$el.replaceWith(el);
|
||||
}
|
||||
this.setElement(el);
|
||||
Tip.createTips(el);
|
||||
},
|
||||
|
||||
_parentRemove: Backbone.View.prototype.remove,
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
var Tip = function(el) {
|
||||
this.el = el;
|
||||
this.title = el.attr('title');
|
||||
this.tipEl = null;
|
||||
this.showTimeout = null;
|
||||
this.hideTimeout = null;
|
||||
this.init();
|
||||
};
|
||||
|
||||
Tip.prototype.init = function() {
|
||||
this.el.removeAttr('title');
|
||||
this.el.mouseenter(this.mouseenter.bind(this)).mouseleave(this.mouseleave.bind(this));
|
||||
};
|
||||
|
||||
Tip.prototype.mouseenter = function() {
|
||||
var that = this;
|
||||
if (this.showTimeout) {
|
||||
return;
|
||||
}
|
||||
this.showTimeout = setTimeout(function() {
|
||||
that.showTimeout = null;
|
||||
if (that.tipEl) {
|
||||
that.tipEl.remove();
|
||||
if (that.hideTimeout) {
|
||||
clearTimeout(that.hideTimeout);
|
||||
that.hideTimeout = null;
|
||||
}
|
||||
}
|
||||
var tipEl = that.tipEl = $('<div></div>').addClass('tip').appendTo('body').html(that.title);
|
||||
var rect = that.el[0].getBoundingClientRect(),
|
||||
tipRect = that.tipEl[0].getBoundingClientRect();
|
||||
var placement = that.el.attr('tip-placement') || that.getAutoPlacement(rect, tipRect);
|
||||
tipEl.addClass('tip--' + placement);
|
||||
var top, left;
|
||||
var offset = 10;
|
||||
switch (placement) {
|
||||
case 'top':
|
||||
top = rect.top - tipRect.height - offset;
|
||||
left = rect.left + rect.width / 2 - tipRect.width / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
top = rect.bottom + offset;
|
||||
left = rect.left + rect.width / 2 - tipRect.width / 2;
|
||||
break;
|
||||
case 'left':
|
||||
top = rect.top + rect.height / 2 - tipRect.height / 2;
|
||||
left = rect.left - tipRect.width - offset;
|
||||
break;
|
||||
case 'right':
|
||||
top = rect.top + rect.height / 2 - tipRect.height / 2;
|
||||
left = rect.right + offset;
|
||||
break;
|
||||
}
|
||||
tipEl.css({ top: top, left: left });
|
||||
}, 200);
|
||||
};
|
||||
|
||||
Tip.prototype.mouseleave = function() {
|
||||
var that = this;
|
||||
if (this.tipEl) {
|
||||
that.tipEl.addClass('tip--hide');
|
||||
this.hideTimeout = setTimeout(function () {
|
||||
that.hideTimeout = null;
|
||||
if (that.tipEl) {
|
||||
that.tipEl.remove();
|
||||
that.tipEl = null;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
if (this.showTimeout) {
|
||||
clearTimeout(this.showTimeout);
|
||||
this.showTimeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
Tip.prototype.getAutoPlacement = function(rect, tipRect) {
|
||||
var padding = 20;
|
||||
var bodyRect = document.body.getBoundingClientRect();
|
||||
var canShowToBottom = bodyRect.bottom - rect.bottom > padding + tipRect.height,
|
||||
canShowToHalfRight = bodyRect.right - rect.right > padding + tipRect.width / 2,
|
||||
canShowToRight = bodyRect.right - rect.right > padding + tipRect.width,
|
||||
canShowToHalfLeft = rect.left > padding + tipRect.width / 2,
|
||||
canShowToLeft = rect.left > padding + tipRect.width;
|
||||
if (canShowToBottom) {
|
||||
if (canShowToLeft && !canShowToHalfRight) {
|
||||
return 'left';
|
||||
} else if (canShowToRight && !canShowToHalfLeft) {
|
||||
return 'right';
|
||||
} else {
|
||||
return 'bottom';
|
||||
}
|
||||
}
|
||||
if (canShowToLeft && !canShowToHalfRight) {
|
||||
return 'left';
|
||||
} else if (canShowToRight && !canShowToHalfLeft) {
|
||||
return 'right';
|
||||
} else {
|
||||
return 'top';
|
||||
}
|
||||
};
|
||||
|
||||
Tip.createTips = function(container) {
|
||||
container.find('[title]').each(function(ix, el) {
|
||||
if (!el._tip) {
|
||||
el._tip = new Tip($(el));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Tip;
|
|
@ -19,6 +19,7 @@ var Backbone = require('backbone'),
|
|||
CopyPaste = require('../../comp/copy-paste'),
|
||||
Format = require('../../util/format'),
|
||||
Locale = require('../../util/locale'),
|
||||
Tip = require('../../util/tip'),
|
||||
FileSaver = require('filesaver'),
|
||||
baron = require('baron'),
|
||||
kdbxweb = require('kdbxweb');
|
||||
|
@ -80,10 +81,12 @@ var DetailsView = Backbone.View.extend({
|
|||
}
|
||||
if (this.model instanceof GroupModel) {
|
||||
this.$el.html(this.groupTemplate());
|
||||
Tip.createTips(this.$el);
|
||||
return;
|
||||
}
|
||||
var model = $.extend({ deleted: this.appModel.filter.trash }, this.model);
|
||||
this.$el.html(this.template(model));
|
||||
Tip.createTips(this.$el);
|
||||
this.setSelectedColor(this.model.color);
|
||||
this.addFieldViews();
|
||||
this.scroll = baron({
|
||||
|
|
|
@ -4,6 +4,7 @@ var Backbone = require('backbone'),
|
|||
Keys = require('../const/keys'),
|
||||
KeyHandler = require('../comp/key-handler'),
|
||||
GeneratorView = require('./generator-view'),
|
||||
Tip = require('../util/tip'),
|
||||
UpdateModel = require('../models/update-model');
|
||||
|
||||
var FooterView = Backbone.View.extend({
|
||||
|
@ -36,6 +37,7 @@ var FooterView = Backbone.View.extend({
|
|||
files: this.model.files,
|
||||
updateAvailable: ['ready', 'found'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0
|
||||
}));
|
||||
Tip.createTips(this.$el);
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
var Backbone = require('backbone'),
|
||||
Scrollable = require('../mixins/scrollable'),
|
||||
IconSelectView = require('./icon-select-view'),
|
||||
Tip = require('../util/tip'),
|
||||
baron = require('baron');
|
||||
|
||||
var GrpView = Backbone.View.extend({
|
||||
|
@ -30,6 +31,7 @@ var GrpView = Backbone.View.extend({
|
|||
enableSearching: this.model.get('enableSearching') !== false,
|
||||
readonly: this.model.get('top')
|
||||
}));
|
||||
Tip.createTips(this.$el);
|
||||
if (!this.model.get('title')) {
|
||||
this.$el.find('#grp__field-title').focus();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ $base-duration: 150ms;
|
|||
$base-timing: ease;
|
||||
$slow-transition-in: $base-duration*2 ease-in;
|
||||
$slow-transition-out: $base-duration ease-out;
|
||||
$tip-transition-in: 500ms $ease-in-expo;
|
||||
$tip-transition-out: $slow-transition-out;
|
||||
|
||||
// Math
|
||||
$sqrt2: 1.41;
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
.tip {
|
||||
position: absolute;
|
||||
padding: $base-padding;
|
||||
border-radius: $base-border-radius;
|
||||
white-space: nowrap;
|
||||
z-index: $z-index-no-modal;
|
||||
pointer-events: none;
|
||||
animation: tip $tip-transition-in;
|
||||
@include dropdown();
|
||||
&--hide.tip, &--hide.tip:before, &--hide.tip:after {
|
||||
transition: all $tip-transition-out;
|
||||
transition-property: color, border-color, background-color, box-shadow;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
&:before, &:after {
|
||||
animation: tip $tip-transition-in;
|
||||
content: " ";
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
$arrow-size-small: 10px 8px;
|
||||
$arrow-size-large: 12px 9px;
|
||||
|
||||
&.tip--bottom:after {
|
||||
@include position(absolute, - nth($arrow-size-small, 2) null null 50%);
|
||||
@include transform(translate(-50%, 0));
|
||||
@include th { @include triangle($arrow-size-small, background-color(), up); }
|
||||
}
|
||||
&.tip--top:after {
|
||||
@include position(absolute, 100% null null 50%);
|
||||
@include transform(translate(-50%, 0));
|
||||
@include th { @include triangle($arrow-size-small, background-color(), down); }
|
||||
}
|
||||
&.tip--left:after {
|
||||
@include position(absolute, 50% null null 100%);
|
||||
@include transform(translate(0, -50%));
|
||||
@include th { @include triangle($arrow-size-small, background-color(), right); }
|
||||
}
|
||||
&.tip--right:after {
|
||||
@include position(absolute, 50% null null (- nth($arrow-size-small, 2)));
|
||||
@include transform(translate(0, -50%));
|
||||
@include th { @include triangle($arrow-size-small, background-color(), left); }
|
||||
}
|
||||
|
||||
&.tip--bottom:before {
|
||||
@include position(absolute, - nth($arrow-size-large, 2) null null 50%);
|
||||
@include transform(translate(-50%, 0));
|
||||
@include th { @include triangle($arrow-size-large, light-border-color(), up); }
|
||||
}
|
||||
&.tip--top:before {
|
||||
@include position(absolute, 100% null null 50%);
|
||||
@include transform(translate(-50%, 0));
|
||||
@include th { @include triangle($arrow-size-large, light-border-color(), down); }
|
||||
}
|
||||
&.tip--left:before {
|
||||
@include position(absolute, 50% null null 100%);
|
||||
@include transform(translate(0, -50%));
|
||||
@include th { @include triangle($arrow-size-large, light-border-color(), right); }
|
||||
}
|
||||
&.tip--right:before {
|
||||
@include position(absolute, 50% null null (- nth($arrow-size-large, 2)));
|
||||
@include transform(translate(0, -50%));
|
||||
@include th { @include triangle($arrow-size-large, light-border-color(), left); }
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tip {
|
||||
from { color: transparent; background-color: transparent; border-color: transparent; box-shadow: none; }
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
@import "common/icon-select";
|
||||
@import "common/modal";
|
||||
@import "common/scroll";
|
||||
@import "common/tip";
|
||||
|
||||
@import "areas/app";
|
||||
@import "areas/details";
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<i class="fa fa-chevron-left"></i> {{res 'detBackToList'}}
|
||||
</div>
|
||||
<div class="details__header">
|
||||
<i class="details__header-color fa fa-bookmark-o" title="{{res 'detSetIconColor'}}">
|
||||
<i class="details__header-color fa fa-bookmark-o" title="{{res 'detSetIconColor'}}" tip-placement="left">
|
||||
<span class="details__colors-popup">
|
||||
<span class="details__colors-popup-item yellow-color fa fa-bookmark-o" data-color="yellow"></span>
|
||||
<span class="details__colors-popup-item green-color fa fa-bookmark-o" data-color="green"></span>
|
||||
|
@ -31,9 +31,9 @@
|
|||
</div>
|
||||
<div class="details__buttons">
|
||||
{{#if deleted~}}
|
||||
<i class="details__buttons-trash-del fa fa-minus-circle" title="{{res 'detDelEntryPerm'}}"></i>
|
||||
<i class="details__buttons-trash-del fa fa-minus-circle" title="{{res 'detDelEntryPerm'}}" tip-placement="top"></i>
|
||||
{{~else~}}
|
||||
<i class="details__buttons-trash fa fa-trash-o" title="{{res 'detDelEntry'}}"></i>
|
||||
<i class="details__buttons-trash fa fa-trash-o" title="{{res 'detDelEntry'}}" tip-placement="top"></i>
|
||||
{{~/if~}}
|
||||
<div class="details__attachments">
|
||||
{{#each attachments as |attachment ix|}}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
{{#if editable}}<i class="menu__item-edit fa fa-cog"></i>{{/if}}
|
||||
{{#ifeq filterKey 'trash'}}<i class="menu__item-empty-trash fa fa-minus-circle" title="{{res 'menuEmptyTrash'}}"></i>{{/ifeq}}
|
||||
{{#ifeq filterKey 'trash'}}<i class="menu__item-empty-trash fa fa-minus-circle" title="{{res 'menuEmptyTrash'}}"
|
||||
tip-placement="right"></i>{{/ifeq}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue