padrino-admin 0.16.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +3 -3
  3. data/Rakefile +0 -21
  4. data/lib/padrino-admin/access_control.rb +17 -4
  5. data/lib/padrino-admin/generators/actions.rb +1 -1
  6. data/lib/padrino-admin/generators/admin_app.rb +9 -11
  7. data/lib/padrino-admin/generators/admin_page.rb +12 -2
  8. data/lib/padrino-admin/generators/orm.rb +30 -47
  9. data/lib/padrino-admin/generators/templates/account/seeds.rb.tt +7 -2
  10. data/lib/padrino-admin/generators/templates/app/controllers/sessions.rb.tt +0 -4
  11. data/lib/padrino-admin/generators/templates/app.rb.tt +4 -1
  12. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/affix.js +63 -36
  13. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/alert.js +18 -11
  14. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/bootstrap.min.js +4 -4
  15. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/button.js +41 -23
  16. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/carousel.js +90 -49
  17. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/collapse.js +86 -44
  18. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/dropdown.js +66 -48
  19. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/modal.js +167 -52
  20. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/popover.js +33 -20
  21. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/scrollspy.js +58 -39
  22. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tab.js +55 -25
  23. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tooltip.js +370 -92
  24. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/transition.js +22 -11
  25. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-3.7.1.min.js +2 -0
  26. data/lib/padrino-admin/generators/templates/assets/stylesheets/application.css +14 -0
  27. data/lib/padrino-admin/generators/templates/assets/stylesheets/bootstrap.css +6 -4
  28. data/lib/padrino-admin/generators/templates/erb/app/base/index.erb.tt +1 -1
  29. data/lib/padrino-admin/generators/templates/erb/app/layouts/application.erb.tt +1 -1
  30. data/lib/padrino-admin/generators/templates/erb/app/sessions/new.erb.tt +1 -1
  31. data/lib/padrino-admin/generators/templates/erb/page/index.erb.tt +2 -2
  32. data/lib/padrino-admin/generators/templates/haml/app/base/index.haml.tt +1 -1
  33. data/lib/padrino-admin/generators/templates/haml/app/layouts/application.haml.tt +1 -1
  34. data/lib/padrino-admin/generators/templates/haml/app/sessions/new.haml.tt +1 -1
  35. data/lib/padrino-admin/generators/templates/slim/app/base/index.slim.tt +1 -1
  36. data/lib/padrino-admin/generators/templates/slim/app/layouts/application.slim.tt +1 -1
  37. data/lib/padrino-admin/generators/templates/slim/app/sessions/new.slim.tt +1 -1
  38. data/lib/padrino-admin/generators/templates/slim/page/index.slim.tt +2 -2
  39. data/lib/padrino-admin/helpers/authentication_helpers.rb +1 -1
  40. data/lib/padrino-admin/helpers/view_helpers.rb +3 -3
  41. data/lib/padrino-admin/locale/orm/pl.yml +0 -11
  42. data/lib/padrino-admin.rb +1 -1
  43. data/padrino-admin.gemspec +0 -2
  44. data/test/generators/test_account_model_generator.rb +1 -60
  45. data/test/generators/test_admin_app_generator.rb +159 -25
  46. data/test/generators/test_admin_page_generator.rb +110 -9
  47. data/test/test_admin_application.rb +123 -0
  48. metadata +9 -124
  49. data/lib/padrino-admin/bootstrap-less/alerts.less +0 -67
  50. data/lib/padrino-admin/bootstrap-less/badges.less +0 -55
  51. data/lib/padrino-admin/bootstrap-less/bootstrap.less +0 -49
  52. data/lib/padrino-admin/bootstrap-less/breadcrumbs.less +0 -26
  53. data/lib/padrino-admin/bootstrap-less/button-groups.less +0 -226
  54. data/lib/padrino-admin/bootstrap-less/buttons.less +0 -155
  55. data/lib/padrino-admin/bootstrap-less/carousel.less +0 -232
  56. data/lib/padrino-admin/bootstrap-less/close.less +0 -33
  57. data/lib/padrino-admin/bootstrap-less/code.less +0 -63
  58. data/lib/padrino-admin/bootstrap-less/component-animations.less +0 -29
  59. data/lib/padrino-admin/bootstrap-less/dropdowns.less +0 -213
  60. data/lib/padrino-admin/bootstrap-less/font-awesome/bordered-pulled.less +0 -16
  61. data/lib/padrino-admin/bootstrap-less/font-awesome/core.less +0 -12
  62. data/lib/padrino-admin/bootstrap-less/font-awesome/fixed-width.less +0 -6
  63. data/lib/padrino-admin/bootstrap-less/font-awesome/font-awesome.less +0 -17
  64. data/lib/padrino-admin/bootstrap-less/font-awesome/icons.less +0 -412
  65. data/lib/padrino-admin/bootstrap-less/font-awesome/larger.less +0 -13
  66. data/lib/padrino-admin/bootstrap-less/font-awesome/list.less +0 -19
  67. data/lib/padrino-admin/bootstrap-less/font-awesome/mixins.less +0 -20
  68. data/lib/padrino-admin/bootstrap-less/font-awesome/path.less +0 -14
  69. data/lib/padrino-admin/bootstrap-less/font-awesome/rotated-flipped.less +0 -9
  70. data/lib/padrino-admin/bootstrap-less/font-awesome/spinning.less +0 -30
  71. data/lib/padrino-admin/bootstrap-less/font-awesome/stacked.less +0 -20
  72. data/lib/padrino-admin/bootstrap-less/font-awesome/variables.less +0 -381
  73. data/lib/padrino-admin/bootstrap-less/forms.less +0 -419
  74. data/lib/padrino-admin/bootstrap-less/glyphicons.less +0 -233
  75. data/lib/padrino-admin/bootstrap-less/grid.less +0 -100
  76. data/lib/padrino-admin/bootstrap-less/input-groups.less +0 -157
  77. data/lib/padrino-admin/bootstrap-less/jumbotron.less +0 -44
  78. data/lib/padrino-admin/bootstrap-less/labels.less +0 -64
  79. data/lib/padrino-admin/bootstrap-less/list-group.less +0 -110
  80. data/lib/padrino-admin/bootstrap-less/media.less +0 -56
  81. data/lib/padrino-admin/bootstrap-less/mixins.less +0 -926
  82. data/lib/padrino-admin/bootstrap-less/modals.less +0 -138
  83. data/lib/padrino-admin/bootstrap-less/navbar.less +0 -616
  84. data/lib/padrino-admin/bootstrap-less/navs.less +0 -242
  85. data/lib/padrino-admin/bootstrap-less/normalize.less +0 -423
  86. data/lib/padrino-admin/bootstrap-less/padrino-admin.less +0 -18
  87. data/lib/padrino-admin/bootstrap-less/pager.less +0 -55
  88. data/lib/padrino-admin/bootstrap-less/pagination.less +0 -88
  89. data/lib/padrino-admin/bootstrap-less/panels.less +0 -230
  90. data/lib/padrino-admin/bootstrap-less/popovers.less +0 -133
  91. data/lib/padrino-admin/bootstrap-less/print.less +0 -101
  92. data/lib/padrino-admin/bootstrap-less/progress-bars.less +0 -80
  93. data/lib/padrino-admin/bootstrap-less/responsive-utilities.less +0 -93
  94. data/lib/padrino-admin/bootstrap-less/scaffolding.less +0 -134
  95. data/lib/padrino-admin/bootstrap-less/tables.less +0 -233
  96. data/lib/padrino-admin/bootstrap-less/theme.less +0 -247
  97. data/lib/padrino-admin/bootstrap-less/thumbnails.less +0 -36
  98. data/lib/padrino-admin/bootstrap-less/tooltip.less +0 -95
  99. data/lib/padrino-admin/bootstrap-less/type.less +0 -296
  100. data/lib/padrino-admin/bootstrap-less/utilities.less +0 -56
  101. data/lib/padrino-admin/bootstrap-less/variables.less +0 -827
  102. data/lib/padrino-admin/bootstrap-less/wells.less +0 -29
  103. data/lib/padrino-admin/generators/templates/account/couchrest.rb.tt +0 -67
  104. data/lib/padrino-admin/generators/templates/account/datamapper.rb.tt +0 -61
  105. data/lib/padrino-admin/generators/templates/account/dynamoid.rb.tt +0 -55
  106. data/lib/padrino-admin/generators/templates/account/minirecord.rb.tt +0 -44
  107. data/lib/padrino-admin/generators/templates/account/mongomapper.rb.tt +0 -47
  108. data/lib/padrino-admin/generators/templates/account/ohm.rb.tt +0 -70
  109. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-1.11.0.min.js +0 -4
  110. data/lib/padrino-admin/locale/orm/cs.yml +0 -12
  111. data/lib/padrino-admin/locale/orm/da.yml +0 -12
  112. data/lib/padrino-admin/locale/orm/de.yml +0 -12
  113. data/lib/padrino-admin/locale/orm/en.yml +0 -12
  114. data/lib/padrino-admin/locale/orm/es.yml +0 -12
  115. data/lib/padrino-admin/locale/orm/fr.yml +0 -12
  116. data/lib/padrino-admin/locale/orm/hu.yml +0 -12
  117. data/lib/padrino-admin/locale/orm/it.yml +0 -12
  118. data/lib/padrino-admin/locale/orm/ja.yml +0 -12
  119. data/lib/padrino-admin/locale/orm/lv.yml +0 -12
  120. data/lib/padrino-admin/locale/orm/nl.yml +0 -12
  121. data/lib/padrino-admin/locale/orm/no.yml +0 -12
  122. data/lib/padrino-admin/locale/orm/pt_br.yml +0 -12
  123. data/lib/padrino-admin/locale/orm/ro.yml +0 -12
  124. data/lib/padrino-admin/locale/orm/ru.yml +0 -12
  125. data/lib/padrino-admin/locale/orm/sv.yml +0 -12
  126. data/lib/padrino-admin/locale/orm/tr.yml +0 -12
  127. data/lib/padrino-admin/locale/orm/uk.yml +0 -12
  128. data/lib/padrino-admin/locale/orm/zh_cn.yml +0 -12
  129. data/lib/padrino-admin/locale/orm/zh_tw.yml +0 -12
@@ -1,47 +1,195 @@
1
1
  /* ========================================================================
2
- * Bootstrap: tooltip.js v3.1.0
3
- * http://getbootstrap.com/javascript/#tooltip
2
+ * Bootstrap: tooltip.js v3.4.1
3
+ * https://getbootstrap.com/docs/3.4/javascript/#tooltip
4
4
  * Inspired by the original jQuery.tipsy by Jason Frame
5
5
  * ========================================================================
6
- * Copyright 2011-2014 Twitter, Inc.
6
+ * Copyright 2011-2019 Twitter, Inc.
7
7
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
8
8
  * ======================================================================== */
9
9
 
10
-
11
10
  +function ($) {
12
11
  'use strict';
13
12
 
13
+ var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']
14
+
15
+ var uriAttrs = [
16
+ 'background',
17
+ 'cite',
18
+ 'href',
19
+ 'itemtype',
20
+ 'longdesc',
21
+ 'poster',
22
+ 'src',
23
+ 'xlink:href'
24
+ ]
25
+
26
+ var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
27
+
28
+ var DefaultWhitelist = {
29
+ // Global attributes allowed on any supplied element below.
30
+ '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
31
+ a: ['target', 'href', 'title', 'rel'],
32
+ area: [],
33
+ b: [],
34
+ br: [],
35
+ col: [],
36
+ code: [],
37
+ div: [],
38
+ em: [],
39
+ hr: [],
40
+ h1: [],
41
+ h2: [],
42
+ h3: [],
43
+ h4: [],
44
+ h5: [],
45
+ h6: [],
46
+ i: [],
47
+ img: ['src', 'alt', 'title', 'width', 'height'],
48
+ li: [],
49
+ ol: [],
50
+ p: [],
51
+ pre: [],
52
+ s: [],
53
+ small: [],
54
+ span: [],
55
+ sub: [],
56
+ sup: [],
57
+ strong: [],
58
+ u: [],
59
+ ul: []
60
+ }
61
+
62
+ /**
63
+ * A pattern that recognizes a commonly useful subset of URLs that are safe.
64
+ *
65
+ * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
66
+ */
67
+ var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi
68
+
69
+ /**
70
+ * A pattern that matches safe data URLs. Only matches image, video and audio types.
71
+ *
72
+ * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
73
+ */
74
+ var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i
75
+
76
+ function allowedAttribute(attr, allowedAttributeList) {
77
+ var attrName = attr.nodeName.toLowerCase()
78
+
79
+ if ($.inArray(attrName, allowedAttributeList) !== -1) {
80
+ if ($.inArray(attrName, uriAttrs) !== -1) {
81
+ return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
82
+ }
83
+
84
+ return true
85
+ }
86
+
87
+ var regExp = $(allowedAttributeList).filter(function (index, value) {
88
+ return value instanceof RegExp
89
+ })
90
+
91
+ // Check if a regular expression validates the attribute.
92
+ for (var i = 0, l = regExp.length; i < l; i++) {
93
+ if (attrName.match(regExp[i])) {
94
+ return true
95
+ }
96
+ }
97
+
98
+ return false
99
+ }
100
+
101
+ function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
102
+ if (unsafeHtml.length === 0) {
103
+ return unsafeHtml
104
+ }
105
+
106
+ if (sanitizeFn && typeof sanitizeFn === 'function') {
107
+ return sanitizeFn(unsafeHtml)
108
+ }
109
+
110
+ // IE 8 and below don't support createHTMLDocument
111
+ if (!document.implementation || !document.implementation.createHTMLDocument) {
112
+ return unsafeHtml
113
+ }
114
+
115
+ var createdDocument = document.implementation.createHTMLDocument('sanitization')
116
+ createdDocument.body.innerHTML = unsafeHtml
117
+
118
+ var whitelistKeys = $.map(whiteList, function (el, i) { return i })
119
+ var elements = $(createdDocument.body).find('*')
120
+
121
+ for (var i = 0, len = elements.length; i < len; i++) {
122
+ var el = elements[i]
123
+ var elName = el.nodeName.toLowerCase()
124
+
125
+ if ($.inArray(elName, whitelistKeys) === -1) {
126
+ el.parentNode.removeChild(el)
127
+
128
+ continue
129
+ }
130
+
131
+ var attributeList = $.map(el.attributes, function (el) { return el })
132
+ var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])
133
+
134
+ for (var j = 0, len2 = attributeList.length; j < len2; j++) {
135
+ if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {
136
+ el.removeAttribute(attributeList[j].nodeName)
137
+ }
138
+ }
139
+ }
140
+
141
+ return createdDocument.body.innerHTML
142
+ }
143
+
14
144
  // TOOLTIP PUBLIC CLASS DEFINITION
15
145
  // ===============================
16
146
 
17
147
  var Tooltip = function (element, options) {
18
- this.type =
19
- this.options =
20
- this.enabled =
21
- this.timeout =
22
- this.hoverState =
148
+ this.type = null
149
+ this.options = null
150
+ this.enabled = null
151
+ this.timeout = null
152
+ this.hoverState = null
23
153
  this.$element = null
154
+ this.inState = null
24
155
 
25
156
  this.init('tooltip', element, options)
26
157
  }
27
158
 
159
+ Tooltip.VERSION = '3.4.1'
160
+
161
+ Tooltip.TRANSITION_DURATION = 150
162
+
28
163
  Tooltip.DEFAULTS = {
29
164
  animation: true,
30
165
  placement: 'top',
31
166
  selector: false,
32
- template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
167
+ template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
33
168
  trigger: 'hover focus',
34
169
  title: '',
35
170
  delay: 0,
36
171
  html: false,
37
- container: false
172
+ container: false,
173
+ viewport: {
174
+ selector: 'body',
175
+ padding: 0
176
+ },
177
+ sanitize : true,
178
+ sanitizeFn : null,
179
+ whiteList : DefaultWhitelist
38
180
  }
39
181
 
40
182
  Tooltip.prototype.init = function (type, element, options) {
41
- this.enabled = true
42
- this.type = type
43
- this.$element = $(element)
44
- this.options = this.getOptions(options)
183
+ this.enabled = true
184
+ this.type = type
185
+ this.$element = $(element)
186
+ this.options = this.getOptions(options)
187
+ this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
188
+ this.inState = { click: false, hover: false, focus: false }
189
+
190
+ if (this.$element[0] instanceof document.constructor && !this.options.selector) {
191
+ throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
192
+ }
45
193
 
46
194
  var triggers = this.options.trigger.split(' ')
47
195
 
@@ -69,7 +217,15 @@
69
217
  }
70
218
 
71
219
  Tooltip.prototype.getOptions = function (options) {
72
- options = $.extend({}, this.getDefaults(), this.$element.data(), options)
220
+ var dataAttributes = this.$element.data()
221
+
222
+ for (var dataAttr in dataAttributes) {
223
+ if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
224
+ delete dataAttributes[dataAttr]
225
+ }
226
+ }
227
+
228
+ options = $.extend({}, this.getDefaults(), dataAttributes, options)
73
229
 
74
230
  if (options.delay && typeof options.delay == 'number') {
75
231
  options.delay = {
@@ -78,6 +234,10 @@
78
234
  }
79
235
  }
80
236
 
237
+ if (options.sanitize) {
238
+ options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)
239
+ }
240
+
81
241
  return options
82
242
  }
83
243
 
@@ -94,7 +254,21 @@
94
254
 
95
255
  Tooltip.prototype.enter = function (obj) {
96
256
  var self = obj instanceof this.constructor ?
97
- obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
257
+ obj : $(obj.currentTarget).data('bs.' + this.type)
258
+
259
+ if (!self) {
260
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
261
+ $(obj.currentTarget).data('bs.' + this.type, self)
262
+ }
263
+
264
+ if (obj instanceof $.Event) {
265
+ self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
266
+ }
267
+
268
+ if (self.tip().hasClass('in') || self.hoverState == 'in') {
269
+ self.hoverState = 'in'
270
+ return
271
+ }
98
272
 
99
273
  clearTimeout(self.timeout)
100
274
 
@@ -107,9 +281,28 @@
107
281
  }, self.options.delay.show)
108
282
  }
109
283
 
284
+ Tooltip.prototype.isInStateTrue = function () {
285
+ for (var key in this.inState) {
286
+ if (this.inState[key]) return true
287
+ }
288
+
289
+ return false
290
+ }
291
+
110
292
  Tooltip.prototype.leave = function (obj) {
111
293
  var self = obj instanceof this.constructor ?
112
- obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
294
+ obj : $(obj.currentTarget).data('bs.' + this.type)
295
+
296
+ if (!self) {
297
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
298
+ $(obj.currentTarget).data('bs.' + this.type, self)
299
+ }
300
+
301
+ if (obj instanceof $.Event) {
302
+ self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
303
+ }
304
+
305
+ if (self.isInStateTrue()) return
113
306
 
114
307
  clearTimeout(self.timeout)
115
308
 
@@ -128,12 +321,17 @@
128
321
  if (this.hasContent() && this.enabled) {
129
322
  this.$element.trigger(e)
130
323
 
131
- if (e.isDefaultPrevented()) return
132
- var that = this;
324
+ var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
325
+ if (e.isDefaultPrevented() || !inDom) return
326
+ var that = this
133
327
 
134
328
  var $tip = this.tip()
135
329
 
330
+ var tipId = this.getUID(this.type)
331
+
136
332
  this.setContent()
333
+ $tip.attr('id', tipId)
334
+ this.$element.attr('aria-describedby', tipId)
137
335
 
138
336
  if (this.options.animation) $tip.addClass('fade')
139
337
 
@@ -149,26 +347,23 @@
149
347
  .detach()
150
348
  .css({ top: 0, left: 0, display: 'block' })
151
349
  .addClass(placement)
350
+ .data('bs.' + this.type, this)
152
351
 
153
- this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
352
+ this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
353
+ this.$element.trigger('inserted.bs.' + this.type)
154
354
 
155
355
  var pos = this.getPosition()
156
356
  var actualWidth = $tip[0].offsetWidth
157
357
  var actualHeight = $tip[0].offsetHeight
158
358
 
159
359
  if (autoPlace) {
160
- var $parent = this.$element.parent()
161
-
162
360
  var orgPlacement = placement
163
- var docScroll = document.documentElement.scrollTop || document.body.scrollTop
164
- var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth()
165
- var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
166
- var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left
167
-
168
- placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :
169
- placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :
170
- placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :
171
- placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :
361
+ var viewportDim = this.getPosition(this.$viewport)
362
+
363
+ placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
364
+ placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
365
+ placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
366
+ placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
172
367
  placement
173
368
 
174
369
  $tip
@@ -179,22 +374,24 @@
179
374
  var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
180
375
 
181
376
  this.applyPlacement(calculatedOffset, placement)
182
- this.hoverState = null
183
377
 
184
- var complete = function() {
378
+ var complete = function () {
379
+ var prevHoverState = that.hoverState
185
380
  that.$element.trigger('shown.bs.' + that.type)
381
+ that.hoverState = null
382
+
383
+ if (prevHoverState == 'out') that.leave(that)
186
384
  }
187
385
 
188
386
  $.support.transition && this.$tip.hasClass('fade') ?
189
387
  $tip
190
- .one($.support.transition.end, complete)
191
- .emulateTransitionEnd(150) :
388
+ .one('bsTransitionEnd', complete)
389
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
192
390
  complete()
193
391
  }
194
392
  }
195
393
 
196
394
  Tooltip.prototype.applyPlacement = function (offset, placement) {
197
- var replace
198
395
  var $tip = this.tip()
199
396
  var width = $tip[0].offsetWidth
200
397
  var height = $tip[0].offsetHeight
@@ -207,8 +404,8 @@
207
404
  if (isNaN(marginTop)) marginTop = 0
208
405
  if (isNaN(marginLeft)) marginLeft = 0
209
406
 
210
- offset.top = offset.top + marginTop
211
- offset.left = offset.left + marginLeft
407
+ offset.top += marginTop
408
+ offset.left += marginLeft
212
409
 
213
410
  // $.fn.offset doesn't round pixel values
214
411
  // so we use setOffset directly with our own function B-0
@@ -228,51 +425,58 @@
228
425
  var actualHeight = $tip[0].offsetHeight
229
426
 
230
427
  if (placement == 'top' && actualHeight != height) {
231
- replace = true
232
428
  offset.top = offset.top + height - actualHeight
233
429
  }
234
430
 
235
- if (/bottom|top/.test(placement)) {
236
- var delta = 0
237
-
238
- if (offset.left < 0) {
239
- delta = offset.left * -2
240
- offset.left = 0
241
-
242
- $tip.offset(offset)
431
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
243
432
 
244
- actualWidth = $tip[0].offsetWidth
245
- actualHeight = $tip[0].offsetHeight
246
- }
433
+ if (delta.left) offset.left += delta.left
434
+ else offset.top += delta.top
247
435
 
248
- this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
249
- } else {
250
- this.replaceArrow(actualHeight - height, actualHeight, 'top')
251
- }
436
+ var isVertical = /top|bottom/.test(placement)
437
+ var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
438
+ var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
252
439
 
253
- if (replace) $tip.offset(offset)
440
+ $tip.offset(offset)
441
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
254
442
  }
255
443
 
256
- Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
257
- this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
444
+ Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
445
+ this.arrow()
446
+ .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
447
+ .css(isVertical ? 'top' : 'left', '')
258
448
  }
259
449
 
260
450
  Tooltip.prototype.setContent = function () {
261
451
  var $tip = this.tip()
262
452
  var title = this.getTitle()
263
453
 
264
- $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
454
+ if (this.options.html) {
455
+ if (this.options.sanitize) {
456
+ title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)
457
+ }
458
+
459
+ $tip.find('.tooltip-inner').html(title)
460
+ } else {
461
+ $tip.find('.tooltip-inner').text(title)
462
+ }
463
+
265
464
  $tip.removeClass('fade in top bottom left right')
266
465
  }
267
466
 
268
- Tooltip.prototype.hide = function () {
467
+ Tooltip.prototype.hide = function (callback) {
269
468
  var that = this
270
- var $tip = this.tip()
469
+ var $tip = $(this.$tip)
271
470
  var e = $.Event('hide.bs.' + this.type)
272
471
 
273
472
  function complete() {
274
473
  if (that.hoverState != 'in') $tip.detach()
275
- that.$element.trigger('hidden.bs.' + that.type)
474
+ if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
475
+ that.$element
476
+ .removeAttr('aria-describedby')
477
+ .trigger('hidden.bs.' + that.type)
478
+ }
479
+ callback && callback()
276
480
  }
277
481
 
278
482
  this.$element.trigger(e)
@@ -281,10 +485,10 @@
281
485
 
282
486
  $tip.removeClass('in')
283
487
 
284
- $.support.transition && this.$tip.hasClass('fade') ?
488
+ $.support.transition && $tip.hasClass('fade') ?
285
489
  $tip
286
- .one($.support.transition.end, complete)
287
- .emulateTransitionEnd(150) :
490
+ .one('bsTransitionEnd', complete)
491
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
288
492
  complete()
289
493
 
290
494
  this.hoverState = null
@@ -294,7 +498,7 @@
294
498
 
295
499
  Tooltip.prototype.fixTitle = function () {
296
500
  var $e = this.$element
297
- if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
501
+ if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
298
502
  $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
299
503
  }
300
504
  }
@@ -303,19 +507,61 @@
303
507
  return this.getTitle()
304
508
  }
305
509
 
306
- Tooltip.prototype.getPosition = function () {
307
- var el = this.$element[0]
308
- return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
309
- width: el.offsetWidth,
310
- height: el.offsetHeight
311
- }, this.$element.offset())
510
+ Tooltip.prototype.getPosition = function ($element) {
511
+ $element = $element || this.$element
512
+
513
+ var el = $element[0]
514
+ var isBody = el.tagName == 'BODY'
515
+
516
+ var elRect = el.getBoundingClientRect()
517
+ if (elRect.width == null) {
518
+ // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
519
+ elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
520
+ }
521
+ var isSvg = window.SVGElement && el instanceof window.SVGElement
522
+ // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
523
+ // See https://github.com/twbs/bootstrap/issues/20280
524
+ var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
525
+ var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
526
+ var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
527
+
528
+ return $.extend({}, elRect, scroll, outerDims, elOffset)
312
529
  }
313
530
 
314
531
  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
315
- return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
316
- placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
532
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
533
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
317
534
  placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
318
- /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
535
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
536
+
537
+ }
538
+
539
+ Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
540
+ var delta = { top: 0, left: 0 }
541
+ if (!this.$viewport) return delta
542
+
543
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
544
+ var viewportDimensions = this.getPosition(this.$viewport)
545
+
546
+ if (/right|left/.test(placement)) {
547
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
548
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
549
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
550
+ delta.top = viewportDimensions.top - topEdgeOffset
551
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
552
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
553
+ }
554
+ } else {
555
+ var leftEdgeOffset = pos.left - viewportPadding
556
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth
557
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
558
+ delta.left = viewportDimensions.left - leftEdgeOffset
559
+ } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
560
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
561
+ }
562
+ }
563
+
564
+ return delta
319
565
  }
320
566
 
321
567
  Tooltip.prototype.getTitle = function () {
@@ -329,20 +575,24 @@
329
575
  return title
330
576
  }
331
577
 
332
- Tooltip.prototype.tip = function () {
333
- return this.$tip = this.$tip || $(this.options.template)
578
+ Tooltip.prototype.getUID = function (prefix) {
579
+ do prefix += ~~(Math.random() * 1000000)
580
+ while (document.getElementById(prefix))
581
+ return prefix
334
582
  }
335
583
 
336
- Tooltip.prototype.arrow = function () {
337
- return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
584
+ Tooltip.prototype.tip = function () {
585
+ if (!this.$tip) {
586
+ this.$tip = $(this.options.template)
587
+ if (this.$tip.length != 1) {
588
+ throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
589
+ }
590
+ }
591
+ return this.$tip
338
592
  }
339
593
 
340
- Tooltip.prototype.validate = function () {
341
- if (!this.$element[0].parentNode) {
342
- this.hide()
343
- this.$element = null
344
- this.options = null
345
- }
594
+ Tooltip.prototype.arrow = function () {
595
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
346
596
  }
347
597
 
348
598
  Tooltip.prototype.enable = function () {
@@ -358,33 +608,61 @@
358
608
  }
359
609
 
360
610
  Tooltip.prototype.toggle = function (e) {
361
- var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
362
- self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
611
+ var self = this
612
+ if (e) {
613
+ self = $(e.currentTarget).data('bs.' + this.type)
614
+ if (!self) {
615
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions())
616
+ $(e.currentTarget).data('bs.' + this.type, self)
617
+ }
618
+ }
619
+
620
+ if (e) {
621
+ self.inState.click = !self.inState.click
622
+ if (self.isInStateTrue()) self.enter(self)
623
+ else self.leave(self)
624
+ } else {
625
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
626
+ }
363
627
  }
364
628
 
365
629
  Tooltip.prototype.destroy = function () {
630
+ var that = this
366
631
  clearTimeout(this.timeout)
367
- this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
632
+ this.hide(function () {
633
+ that.$element.off('.' + that.type).removeData('bs.' + that.type)
634
+ if (that.$tip) {
635
+ that.$tip.detach()
636
+ }
637
+ that.$tip = null
638
+ that.$arrow = null
639
+ that.$viewport = null
640
+ that.$element = null
641
+ })
368
642
  }
369
643
 
644
+ Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {
645
+ return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)
646
+ }
370
647
 
371
648
  // TOOLTIP PLUGIN DEFINITION
372
649
  // =========================
373
650
 
374
- var old = $.fn.tooltip
375
-
376
- $.fn.tooltip = function (option) {
651
+ function Plugin(option) {
377
652
  return this.each(function () {
378
653
  var $this = $(this)
379
654
  var data = $this.data('bs.tooltip')
380
655
  var options = typeof option == 'object' && option
381
656
 
382
- if (!data && option == 'destroy') return
657
+ if (!data && /destroy|hide/.test(option)) return
383
658
  if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
384
659
  if (typeof option == 'string') data[option]()
385
660
  })
386
661
  }
387
662
 
663
+ var old = $.fn.tooltip
664
+
665
+ $.fn.tooltip = Plugin
388
666
  $.fn.tooltip.Constructor = Tooltip
389
667
 
390
668