tips 0.0.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9684df1fded8d68ca41ee1ce6f427df5af471b34
4
+ data.tar.gz: dfbf087e71d2d7d8d9553bf560e00e1254b45081
5
+ SHA512:
6
+ metadata.gz: 405cd0a5dcb1b15ee664b066a23149bb893dc4aff4d18c3b6e5635c20c2911a3339b6d7fe1a588a520af4f19443dfcb293a1d501e32c0fcdb013bd176e8204d3
7
+ data.tar.gz: a1f9c2b17f0230b56b385db8d3a756689eb4cc819688405fd86d51f4efbdd63143ac1ee771ee5f6ad9b90274b1e4ba6d4a55c3adc5b875b47a3fed0dddcf052d
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tips.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ryan Guerrettaz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Tips
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'tips'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install tips
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/tips/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
Binary file
@@ -0,0 +1,582 @@
1
+ ;(function setup(window){
2
+
3
+ function initTips(){
4
+ function Validation(config, settableAttrs, acceptableValues) {
5
+
6
+ function logInvalidKeyError(key){
7
+ console.log(key + ' is not a valid config key.')
8
+ }
9
+
10
+ function logInvalidValueError(attrKey, attrVal){
11
+ console.log(attrVal + ' is not a valid value for ' + attrKey)
12
+ }
13
+
14
+ Object.keys(config).forEach(function(key){
15
+ if (settableAttrs.indexOf(key) > -1) {
16
+ var value = config[key]
17
+
18
+ if (acceptableValues[key].indexOf(config[key]) === -1) {
19
+ logInvalidValueError(key, value)
20
+
21
+ // Remove the invalid key from the config hash
22
+ config[key] = undefined
23
+ }
24
+ } else logInvalidKeyError(key)
25
+ })
26
+ }
27
+
28
+ //
29
+ //
30
+ // STORAGE DEVICE
31
+ //
32
+ //
33
+ var tipsCustomStorageDevice;
34
+ var storageDevice;
35
+ var defaultStorageDevice = {
36
+ optOutKey: 'tips-opted-out',
37
+
38
+ addTip: function(tipName){
39
+ localStorage.setItem(tipName, true);
40
+ },
41
+
42
+ tipHasBeenSeen: function(tipName){
43
+ return !!localStorage.getItem(tipName);
44
+ },
45
+
46
+ optOut: function(){
47
+ localStorage.setItem(this.optOutKey, true);
48
+ },
49
+
50
+ userHasOptedOut: function(){
51
+ return !!localStorage.getItem(this.optOutKey);
52
+ },
53
+
54
+ removeAll: function(tipNames){
55
+ tipNames.push(this.optOutKey)
56
+ tipNames.forEach(function(key){
57
+ localStorage.removeItem(key)
58
+ })
59
+ }
60
+ };
61
+
62
+ if (tipsCustomStorageDevice == undefined) storageDevice = defaultStorageDevice;
63
+ else storageDevice = tipsCustomStorageDevice();
64
+
65
+ //
66
+ //
67
+ // TIPS
68
+ //
69
+ //
70
+ var Tips = {
71
+ list: [],
72
+
73
+ userOptedOut: function(){ return storageDevice.userHasOptedOut() },
74
+
75
+ reset: function(){ storageDevice.removeAll(this.tipNames()) },
76
+
77
+ tipNames: function(){
78
+ var names = [];
79
+ this.list.forEach( function(tip){ names.push(tip.name) } )
80
+ return names
81
+ },
82
+
83
+ showNextTip: function(){
84
+ // Guard clause
85
+ if (this.userOptedOut()) return
86
+ var unseen = Tips.unseen()
87
+ if (unseen.length != 0) unseen[0].hotSpot.show()
88
+ },
89
+
90
+ unseen: function() {
91
+ var unseen = [];
92
+ this.list.forEach(function(tip){
93
+ if (!tip.hasBeenSeen()) unseen.push(tip)
94
+ })
95
+ return unseen
96
+ }
97
+ }
98
+
99
+ //
100
+ //
101
+ // TIP
102
+ //
103
+ //
104
+ function Tip(el) {
105
+ this.name = 'tips:' + el.getAttribute('data-tips-name');
106
+ this.priority = el.getAttribute('data-tips-priority') || 1;
107
+ this.pages = el.getAttribute('data-tips-pages') || 1;
108
+ this.tipNode = el;
109
+ this.height = 246;
110
+ this.width = 485;
111
+ this.hotspotPosition = el.getAttribute('data-tips-hot-spot-position') || 'right';
112
+ this.cardPosition = el.getAttribute('data-tips-card-position') || 'right';
113
+ this.tipCard = new TipCard(el, this);
114
+ this.hotSpot = new HotSpot(this);
115
+ }
116
+
117
+ Tip.prototype.hasBeenSeen = function() {
118
+ //Look in storage device for tip seen
119
+ return !!storageDevice.tipHasBeenSeen(this.name)
120
+ }
121
+
122
+ //
123
+ //
124
+ // HOT SPOT
125
+ //
126
+ //
127
+ function HotSpot(tip) {
128
+ var hotSpot = {};
129
+ var hotSpotSpecs = {
130
+ width: 56,
131
+ height: 56,
132
+ middle: 56/2
133
+ };
134
+
135
+ hotSpot.tipCard = tip.tipCard;
136
+ hotSpot.tip = tip;
137
+ hotSpot.position = tip.hotspotPosition;
138
+ hotSpot.tipNode = tip.tipNode;
139
+ hotSpot.tipName = tip.name;
140
+ hotSpot.hotSpotNode = createHotSpotNode();
141
+
142
+ hotSpot.show = function(){
143
+ this.hotSpotNode.style.display = '';
144
+ }
145
+
146
+ hotSpot.hide = function(){
147
+ this.hotSpotNode.style.display = 'none';
148
+ }
149
+
150
+ // Private functions
151
+
152
+ function createHotSpotNode(){
153
+ var bounds = hotSpot.tipNode.getBoundingClientRect();
154
+ var hotSpotNode = document.createElement('div');
155
+ var offsetLeft = calcualteHotSpotOffsetLeft(bounds);
156
+ var offsetTop = calculateHotSpotOffsetTop(bounds);
157
+
158
+ hotSpotNode.className = 'tips-throbber';
159
+ hotSpotNode.style.position = 'absolute';
160
+ hotSpotNode.style.top = offsetTop + 'px';
161
+ hotSpotNode.style.left = offsetLeft + 'px';
162
+ hotSpotNode.style.display = 'none';
163
+
164
+ addHotSpotListener(hotSpotNode);
165
+
166
+ document.body.appendChild(hotSpotNode);
167
+
168
+ return hotSpotNode
169
+ }
170
+
171
+ function calculateHotSpotOffsetTop(bounds) {
172
+ var topPxLength = 0;
173
+ var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset :
174
+ (document.documentElement ||
175
+ document.body.parentNode || document.body).scrollTop;
176
+ var topPositions = ['top-left', 'top', 'top-right'];
177
+ var bottomPositions = ['bottom-left', 'bottom', 'bottom-right'];
178
+ var middlePositions = ['middle', 'left', 'right'];
179
+
180
+ if (topPositions.indexOf(hotSpot.position) !== -1) {
181
+ topPxLength = bounds.top - hotSpotSpecs.middle + scrollTop;
182
+ } else if (bottomPositions.indexOf(hotSpot.position) !== -1) {
183
+ topPxLength = bounds.top + bounds.height - hotSpotSpecs.middle +
184
+ scrollTop;
185
+ } else if (middlePositions.indexOf(hotSpot.position) !== -1) {
186
+ topPxLength = bounds.top + bounds.height/2 - hotSpotSpecs.middle +
187
+ scrollTop;
188
+ } else { console.log(hotSpot.position + ' is an invalid position hotSpot position') };
189
+
190
+ return topPxLength;
191
+ }
192
+
193
+ function calcualteHotSpotOffsetLeft(bounds) {
194
+ var leftPositions = ['top-left', 'left', 'bottom-left'];
195
+ var rightPositions = ['top-right', 'right', 'bottom-right'];
196
+ var middlePositions = ['middle', 'top', 'bottom'];
197
+ var scrollLeft = (window.pageXOffset !== undefined) ?
198
+ window.pageXOffset : (document.documentElement ||
199
+ document.body.parentNode ||
200
+ document.body).scrollLeft;
201
+
202
+ if (leftPositions.indexOf(hotSpot.position) !== -1) {
203
+ return bounds.left - hotSpotSpecs.middle + scrollLeft;
204
+ } else if (rightPositions.indexOf(hotSpot.position) !== -1) {
205
+ return bounds.left + bounds.width - hotSpotSpecs.middle + scrollLeft;
206
+ } else if (middlePositions.indexOf(hotSpot.position) !== -1) {
207
+ return bounds.left + bounds.width/2 - hotSpotSpecs.middle + scrollLeft;
208
+ } else { console.log(hotSpot.position + ' is an invalid hotSpot position') };
209
+ }
210
+
211
+ function addHotSpotListener(hotSpotNode){
212
+ hotSpotNode.addEventListener('click', function(){
213
+ storeThatTipHasBeenSeen();
214
+ showTipCard();
215
+ hotSpot.hide();
216
+ })
217
+ }
218
+
219
+ function storeThatTipHasBeenSeen(){
220
+ storageDevice.addTip(hotSpot.tipName);
221
+ }
222
+
223
+ function showTipCard(){
224
+ hotSpot.tipCard.show();
225
+ }
226
+
227
+ return hotSpot
228
+ }
229
+
230
+ //
231
+ //
232
+ // TIP CARD
233
+ //
234
+ //
235
+ function TipCard(tipNode, tip) {
236
+ var tipCard = {};
237
+ tipCard.tip = tip;
238
+ tipCard.tipNode = tipNode;
239
+ tipCard.content = JSON.parse(tipNode.getAttribute('data-tips-content'));
240
+ tipCard.position = tip.cardPosition;
241
+ tipCard.cardNode = createCardNode();
242
+ tipCard.scritNode = createScritNode();
243
+
244
+ tipCard.show = function(){
245
+ this.cardNode.style.display = '';
246
+ this.scritNode.style.display = '';
247
+
248
+ // Highlight the tip node
249
+ // For z-index to work the tipNode must have a positon in the following:
250
+ // ['absolute', 'relative', 'fixed']
251
+ this.tipNode.style.zIndex = 9999;
252
+ };
253
+
254
+ tipCard.hide = function(){
255
+ this.cardNode.style.display = 'none';
256
+ this.scritNode.style.display = 'none';
257
+ this.tipNode.style.zIndex = 0;
258
+ Tips.showNextTip();
259
+ }
260
+
261
+ // Private functions
262
+
263
+ function createCardNode(){
264
+ var bounds = tipCard.tipNode.getBoundingClientRect();
265
+ var cardNode = document.createElement('div');
266
+ cardNode.className = 'tips-card tips-card-' + tipCard.position;
267
+ cardNode.appendChild(createContent());
268
+ cardNode.appendChild(createOptOut())
269
+ cardNode.style.display = 'none';
270
+ cardNode.style.position = 'absolute';
271
+ cardNode.style.width = tip.width + 'px';
272
+ cardNode.style.left = calculateOffsetLeft(bounds, cardNode);
273
+ cardNode.style.top = calculateOffsetTop(bounds, cardNode);
274
+ cardNode.style.zIndex = 9999;
275
+
276
+ document.body.appendChild(cardNode);
277
+ return cardNode;
278
+ }
279
+
280
+ function createContent(){
281
+ var pageCount = tip.pages
282
+ var content = document.createElement('div')
283
+ content.className = 'tips-content-wrapper'
284
+
285
+ for(var i = 1; i <= pageCount; i++){
286
+ var lastPage = false;
287
+
288
+ if(i == pageCount) lastPage = true;
289
+
290
+ content.appendChild(createPage(i, lastPage))
291
+ }
292
+
293
+ return content
294
+ }
295
+
296
+ function createPage(pageNumber, lastPage){
297
+ var page = document.createElement('div')
298
+
299
+ if (pageNumber === 1) {
300
+ page.className = 'tips-content-page-' + pageNumber +
301
+ ' tips-last-page-' + lastPage + ' tips-active-tip' +
302
+ ' tips-page';
303
+ } else {
304
+ page.className = 'tips-content-page-' + pageNumber +
305
+ ' tips-last-page-' + lastPage + ' tips-page';
306
+ }
307
+
308
+ page.innerHTML = headerHtml(pageNumber) + bodyHtml(pageNumber);
309
+ page.zIndex = 100 - pageNumber;
310
+
311
+ if (pageNumber !== 1) page.style.display = 'none';
312
+
313
+ page.appendChild(createNavigation(lastPage, pageNumber))
314
+
315
+ return page
316
+ }
317
+
318
+ function headerHtml(pageNumber) {
319
+ return '<h2>' + tipCard.content['page' + pageNumber + 'Header'] +
320
+ '</h2>'
321
+ }
322
+
323
+ function bodyHtml(pageNumber) {
324
+ return '<p>' + tipCard.content['page' + pageNumber + 'Body'] +
325
+ '</p>'
326
+ }
327
+
328
+ function createNavigation(lastPage, pageNumber) {
329
+ var wrapper = document.createElement('div')
330
+ wrapper.className = 'tips-navigation-wrapper'
331
+
332
+ if (tip.pages > 1) {
333
+ wrapper.appendChild(createNavigationalDots(pageNumber))
334
+ }
335
+
336
+ if (lastPage) { wrapper.appendChild(createDoneButton()) }
337
+ else { wrapper.appendChild(createNextButton()) }
338
+
339
+ return wrapper
340
+ }
341
+
342
+ function createNextButton(){
343
+ var wrapper = document.createElement('div');
344
+ wrapper.className = 'tips-btn-wrapper'
345
+ var btn = document.createElement('button');
346
+ btn.className = 'tips-btn tips-next-btn tips-green';
347
+ btn.textContent = 'Next';
348
+ addNextButtonListener(btn);
349
+ wrapper.appendChild(btn)
350
+
351
+ return wrapper
352
+ }
353
+
354
+ function addNextButtonListener(btn){
355
+ btn.addEventListener('click', function(e){
356
+ e.preventDefault();
357
+
358
+ // TODO: traversing DOM seems nasty, must be better way
359
+ removeClass(this.parentNode.parentNode.parentNode, 'tips-active-tip')
360
+ this.parentNode.parentNode.parentNode.style.display = 'none';
361
+
362
+ this.parentNode.parentNode.parentNode.nextSibling.style.display = '';
363
+ addClass(this.parentNode.parentNode.parentNode.nextSibling, 'tips-active-tip')
364
+ })
365
+ }
366
+
367
+ function createDoneButton() {
368
+ var wrapper = document.createElement('div');
369
+ wrapper.className = 'tips-btn-wrapper';
370
+ var btn = document.createElement('button')
371
+ btn.className = 'tips-btn tips-done-btn tips-red';
372
+ btn.textContent = 'Done';
373
+ addDoneButtonListener(btn);
374
+ wrapper.appendChild(btn)
375
+
376
+ return wrapper
377
+ }
378
+
379
+ function addDoneButtonListener(btn){
380
+ btn.addEventListener('click', function(){
381
+ tipCard.hide();
382
+ })
383
+ }
384
+
385
+ function createNavigationalDots(pageNumber){
386
+ var dots = document.createElement('div');
387
+ dots.className = 'tips-navigational-dots-wrapper'
388
+
389
+ for(var i = 1; i <= tip.pages; i++){
390
+ dots.appendChild(createDot(pageNumber, i))
391
+ }
392
+
393
+ return dots;
394
+ }
395
+
396
+ function createDot(parentPageNumber, pageNumber){
397
+ // var dot = document.createElement('div');
398
+ var dotClass = 'tips-navigational-dot';
399
+ var activeDotClass = 'tips-active-dot';
400
+ var dot = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
401
+ var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
402
+ dot.setAttribute('data-tips-page-number', pageNumber);
403
+
404
+ if(parentPageNumber == pageNumber) dot.setAttribute('class', dotClass + ' ' + activeDotClass)
405
+ else dot.setAttribute('class', dotClass)
406
+
407
+ dot.setAttribute('height', '6');
408
+ dot.setAttribute('width' ,'6');
409
+
410
+ circle.setAttribute('cx', '3');
411
+ circle.setAttribute('cy', '3');
412
+ circle.setAttribute('r', '3');
413
+ circle.setAttribute('fill', 'red');
414
+
415
+ dot.appendChild(circle);
416
+
417
+ addDotListener(dot)
418
+
419
+ return dot;
420
+ }
421
+
422
+ function addDotListener(dot){
423
+ dot.addEventListener('click', function(){
424
+ hideActiveDotsCard(dot);
425
+ showClickedDotsCard(dot);
426
+ })
427
+ }
428
+
429
+ function hideActiveDotsCard(dot) {
430
+ // TODO: YUCK!!!!! Safari doesn't support .closest
431
+ // Surely there must be a cleaner/safer way to find this el
432
+ dot.parentNode.parentNode.parentNode.style.display = 'none';
433
+ }
434
+
435
+ function showClickedDotsCard(dot) {
436
+ var pageNumber = dot.getAttribute('data-tips-page-number')
437
+
438
+ // TODO: find cleaner way to find parent el
439
+ var pageToShow = dot.parentNode.parentNode.parentNode.parentNode
440
+ .getElementsByClassName('tips-content-page-' +
441
+ pageNumber)[0];
442
+ pageToShow.style.display = '';
443
+ }
444
+
445
+
446
+ function createOptOut() {
447
+ var optOutWrapper = document.createElement('span');
448
+ var optOutLink = document.createElement('a');
449
+ optOutWrapper.className = 'tips-opt-out';
450
+ optOutWrapper.textContent = 'Seen this before?'
451
+ optOutLink.className = 'tips-opt-out-link'
452
+ optOutLink.textContent = ' Opt out of these tips.'
453
+ optOutLink.setAttribute('href', '#')
454
+ optOutWrapper.appendChild(optOutLink)
455
+ addOptOutListener(optOutLink)
456
+ return optOutWrapper
457
+ }
458
+
459
+ function addOptOutListener(optOutLink) {
460
+ optOutLink.addEventListener('click', function(e){
461
+ e.preventDefault();
462
+ storageDevice.optOut();
463
+ tipCard.hide();
464
+ })
465
+ };
466
+
467
+ // TODO: A tipCard should know the optimal location for its speech
468
+ // bubble point based on the window boundaries. For now it the speech
469
+ // bubble point will always be centered on the tipNode
470
+ function calculateOffsetLeft(bounds, cardNode){
471
+ // length of speech box tip
472
+ var pointerLength = 15;
473
+ var scrollLeft = (window.pageXOffset !== undefined) ?
474
+ window.pageXOffset : (document.documentElement ||
475
+ document.body.parentNode ||
476
+ document.body).scrollLeft;
477
+ if(tipCard.position === 'right'){
478
+ return bounds.left + bounds.width + pointerLength +
479
+ scrollLeft + 'px';
480
+ } else if(tipCard.position === 'left'){
481
+ return bounds.left - cardNode.style.width.replace('px', '')/1 -
482
+ pointerLength + scrollLeft + 'px';
483
+ } else if (['top', 'bottom'].indexOf(tipCard.position) !== -1){
484
+ return bounds.left - cardNode.style.width.replace('px', '')/2 +
485
+ bounds.width/2 + scrollLeft + 'px';
486
+ } else {
487
+ console.log(tipCard.position + ' is an invalid tipCard position.')
488
+
489
+ // Same position as right as it's the default position, for now
490
+ return bounds.left + bounds.width + scrollLeft + 'px';
491
+ }
492
+ };
493
+
494
+ function calculateOffsetTop(bounds, cardNode){
495
+ var pointerLength = 15;
496
+ var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset :
497
+ (document.documentElement ||
498
+ document.body.parentNode || document.body).scrollTop;
499
+
500
+ if(['right', 'left'].indexOf(tipCard.position) !== -1){
501
+ return bounds.top + bounds.height/2 - tip.height/2 + scrollTop + 'px';
502
+ } else if(tipCard.position === 'top'){
503
+ return bounds.top - tip.height - pointerLength + scrollTop + 'px';
504
+ } else if(tipCard.position === 'bottom'){
505
+ return bounds.top + bounds.height + pointerLength + scrollTop + 'px';
506
+ } else {
507
+ // Same position as right as it's the default position, for now
508
+ return bounds.top - bounds.height/2 +
509
+ tip.height/2 + scrollTop + 'px';
510
+ }
511
+ };
512
+
513
+ function createScritNode(){
514
+ var scritNode = document.createElement('div')
515
+ scritNode.className = 'shadow';
516
+ scritNode.style.height = '100%';
517
+ scritNode.style.width = '100%';
518
+ scritNode.style.zIndex = 9998;
519
+ scritNode.style.backgroundColor = 'rgb(95, 95, 95)';
520
+ scritNode.style.opacity = '0.5';
521
+ scritNode.style.position = 'fixed';
522
+ scritNode.style.left = '0px';
523
+ scritNode.style.top = '0px';
524
+ scritNode.style.display = 'none';
525
+
526
+ addScritNodeListener(scritNode);
527
+
528
+ document.body.appendChild(scritNode)
529
+ return scritNode
530
+ }
531
+
532
+ function addScritNodeListener(scritNode){
533
+ scritNode.addEventListener('click',function(){
534
+ tipCard.hide();
535
+ })
536
+ }
537
+
538
+ return tipCard;
539
+ }
540
+
541
+ // Helper functions
542
+
543
+ function addClass(node, className) {
544
+ node.className = node.className + ' ' + className;
545
+ }
546
+
547
+ function removeClass(node, className) {
548
+ node.className = node.className.replace(className, '')
549
+ }
550
+
551
+ // Extend String object with capitalize method
552
+ String.prototype.capitalize = function() {
553
+ return this.charAt(0).toUpperCase() + this.slice(1);
554
+ }
555
+
556
+ function createTips(){
557
+ var tipNodes = document.querySelectorAll('[data-tips-single-item]');
558
+
559
+ for(i = 0; i < tipNodes.length; ++i) {
560
+ var tip = new Tip(tipNodes[i])
561
+ Tips.list.push(tip)
562
+ }
563
+ };
564
+
565
+ // Sort list to make TipCard showing easier
566
+ function sortTipsList(list){
567
+ Tips.list.sort(function(a,b) {return a.priority - b.priority})
568
+ }
569
+
570
+ createTips();
571
+ sortTipsList();
572
+ Tips.showNextTip();
573
+
574
+ return Tips;
575
+ }
576
+
577
+ if(typeof(Tips) === 'undefined') window.Tips = initTips();
578
+ else console.log('Tips already defined.');
579
+
580
+ window.Tips.reset()
581
+
582
+ })(window);
@@ -0,0 +1,279 @@
1
+ .tips-throbber {
2
+ width: 58px;
3
+ height: 58px;
4
+ cursor: pointer;
5
+ z-index: 10;
6
+ background-image: url('/assets/tip_throbber.png');
7
+ -webkit-animation: throb .5s steps(12) alternate infinite;
8
+ -moz-animation: throb .5s steps(12) alternate infinite;
9
+ animation: throb .5s steps(12) alternate infinite;
10
+ }
11
+
12
+ @keyframes throb{
13
+ from{background-position:0 0}
14
+ to{background-position:-696px 0}
15
+ }
16
+
17
+ @-webkit-keyframes throb{
18
+ from{background-position:0 0}
19
+ to{background-position:100% 0}
20
+ }
21
+
22
+ @-moz-keyframes throb{
23
+ from{background-position:0 0}
24
+ to{background-position:-696px 0}
25
+ }
26
+
27
+ .tips-card {
28
+ position: relative;
29
+ -webkit-font-smoothing: antialiased;
30
+ -webkit-user-select: none;
31
+ box-sizing: border-box;
32
+ color: rgb(85, 84, 89);
33
+ display: block;
34
+ font-size: 16px;
35
+ min-height: 246px;
36
+ line-height: 20px;
37
+ width: 436px;
38
+ border: 2px solid #7F7F7F;
39
+ background: #FFFFFF;
40
+ -webkit-border-radius: 10px;
41
+ -moz-border-radius: 10px;
42
+ border-radius: 10px;
43
+ padding: 30px;
44
+ }
45
+
46
+ .tips-card, p, h2, a, li {
47
+ font-family: Lato, sans-serif !important;
48
+ }
49
+
50
+ .tips-card:after {
51
+ content: '';
52
+ position: absolute;
53
+ border-style: solid;
54
+ border-color: transparent #FFFFFF;
55
+ display: block;
56
+ width: 0;
57
+ z-index: 1;
58
+ }
59
+
60
+ .tips-card-right:after {
61
+ border-width: 15px 15px 15px 0;
62
+ left: -15px;
63
+ top: 118px;
64
+ }
65
+
66
+ .tips-card-left:after {
67
+ border-width: 15px 0 15px 15px;
68
+ right: -15px;
69
+ top: 118px;
70
+ }
71
+
72
+ .tips-card-top:after {
73
+ content: '';
74
+ position: absolute;
75
+ border-style: solid;
76
+ border-width: 15px 15px 0;
77
+ border-color: #FFFFFF transparent;
78
+ display: block;
79
+ width: 0;
80
+ z-index: 1;
81
+ bottom: -15px;
82
+ left: 228px;
83
+ }
84
+
85
+ .tips-card-bottom:after {
86
+ content: '';
87
+ position: absolute;
88
+ border-style: solid;
89
+ border-width: 0 15px 15px;
90
+ border-color: #FFFFFF transparent;
91
+ display: block;
92
+ width: 0;
93
+ z-index: 1;
94
+ top: -15px;
95
+ left: 228px;
96
+ }
97
+
98
+ .tips-card:before {
99
+ content: '';
100
+ position: absolute;
101
+ border-style: solid;
102
+ border-color: transparent #7F7F7F;
103
+ display: block;
104
+ width: 0;
105
+ z-index: 0;
106
+ }
107
+
108
+ .tips-card-right:before{
109
+ border-width: 16px 16px 16px 0;
110
+ left: -18px;
111
+ top: 117px;
112
+ }
113
+
114
+ .tips-card-left:before{
115
+ border-width: 16px 0 16px 16px;
116
+ right: -18px;
117
+ top: 117px;
118
+ }
119
+
120
+ .tips-card-top:before{
121
+ content: '';
122
+ position: absolute;
123
+ border-style: solid;
124
+ border-width: 16px 16px 0;
125
+ border-color: #7F7F7F transparent;
126
+ display: block;
127
+ width: 0;
128
+ z-index: 0;
129
+ bottom: -18px;
130
+ left: 227px;
131
+ }
132
+
133
+ .tips-card-bottom:before{
134
+ content: '';
135
+ position: absolute;
136
+ border-style: solid;
137
+ border-width: 0 16px 16px;
138
+ border-color: #7F7F7F transparent;
139
+ display: block;
140
+ width: 0;
141
+ z-index: 0;
142
+ top: -18px;
143
+ left: 227px;
144
+ }
145
+
146
+ .tips-card h2 {
147
+ margin: 0 0 10px;
148
+ font-size: 1.3em;
149
+ }
150
+
151
+ .tips-card p {
152
+ margin: 0px;
153
+ line-height: 20px;
154
+ font-size: 15px;
155
+ letter-spacing: 0.5px;
156
+ font-width: 400;
157
+ }
158
+
159
+ .tips-opt-out {
160
+ position: absolute;
161
+ right: 30px;
162
+ bottom: 6px;
163
+ font-size: 11px;
164
+ color: #aaaaaa
165
+ }
166
+
167
+ .tips-opt-out-link {
168
+ text-decoration: none;
169
+ color: #4A90E2
170
+ }
171
+
172
+ .tips-opt-out-link:hover{
173
+ background-color: inherit;
174
+ color: #407ABE;
175
+ }
176
+
177
+ .tips-navigational-dots-wrapper {
178
+ position: relative;
179
+ display: inline-block;
180
+ margin: 0 auto;
181
+ }
182
+
183
+ .tips-navigational-dot {
184
+ padding: 0 5px;
185
+ height: 8px;
186
+ width: 8px;
187
+ margin: 0 auto;
188
+ display: inline-block;
189
+ position: relative;
190
+ cursor: pointer;
191
+ display: inline-block;
192
+ }
193
+
194
+ .tips-navigational-dot circle {
195
+ fill: #ccc;
196
+ }
197
+
198
+ .tips-active-dot circle {
199
+ fill: #777777;
200
+ }
201
+
202
+ .tips-navigation-wrapper {
203
+ position: absolute;
204
+ right: 20px;
205
+ bottom: 40px;
206
+ left: 12px;
207
+ text-align: center;
208
+ }
209
+
210
+ .tips-btn-wrapper {
211
+ display: inline-block;
212
+ position: relative;
213
+ float: right;
214
+ bottom: 6px;
215
+ height: 20px;
216
+ width: 20px;
217
+ }
218
+
219
+ /* Ripped these buttons off */
220
+ .tips-btn {
221
+ border: 0 none;
222
+ border-radius: 4px;
223
+ color: #FFFFFF;
224
+ cursor: pointer;
225
+ position: absolute;
226
+ right: 10px;
227
+ display: inline-block;
228
+ font-family: Lato,sans-serif;
229
+ font-size: 13px;
230
+ font-weight: bold;
231
+ line-height: 16px;
232
+ margin-bottom: 0;
233
+ padding: 6px 17px;
234
+ text-transform: none;
235
+ transition: all 0.3s ease 0s;
236
+ -moz-transition: all 0.3s ease 0s;
237
+ -webkit-transition: all 0.3s ease 0s;
238
+ text-align: center; /* DELETE WHEN WIDTH AUTO */
239
+ }
240
+
241
+ .tips-btn.tips-dark {
242
+ background: none repeat scroll 0 0 #444444;
243
+ }
244
+
245
+ .tips-btn.tips-dark:hover {
246
+ background: none repeat scroll 0 0 #2DB6CF;
247
+ }
248
+
249
+ .tips-btn.tips-light {
250
+ background: none repeat scroll 0 0 #999999;
251
+ }
252
+
253
+ .tips-btn.tips-light:hover {
254
+ background: none repeat scroll 0 0 #444444;
255
+ }
256
+
257
+ .tips-btn.tips-green {
258
+ background: none repeat scroll 0 0 #46b98a;
259
+ }
260
+
261
+ .tips-btn.tips-green:hover {
262
+ background: none repeat scroll 0 0 #444444;
263
+ }
264
+
265
+ .tips-btn.tips-blue {
266
+ background: none repeat scroll 0 0 #2DB6CF;
267
+ }
268
+
269
+ .tips-btn.tips-blue:hover {
270
+ background: none repeat scroll 0 0 #444444;
271
+ }
272
+
273
+ .tips-btn.tips-red {
274
+ background: none repeat scroll 0 0 #E0645C;
275
+ }
276
+
277
+ .tips-btn.tips-red:hover {
278
+ background: none repeat scroll 0 0 #999999;
279
+ }
@@ -0,0 +1,4 @@
1
+ module Tips
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Tips
2
+ VERSION = "0.0.2"
3
+ end
data/lib/tips.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "tips/version"
2
+ require "tips/engine"
3
+
4
+ module Tips
5
+ # Your code goes here...
6
+ end
data/tips.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tips/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tips"
8
+ spec.version = Tips::VERSION
9
+ spec.authors = ["Ryan Guerrettaz"]
10
+ spec.email = ["rguerrettaz@yahoo.com"]
11
+ spec.summary = "Slack inspired onboarding tips"
12
+ spec.description = ""
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tips
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Guerrettaz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: ''
42
+ email:
43
+ - rguerrettaz@yahoo.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - app/assets/images/tip_throbber.png
54
+ - app/assets/javascripts/tips.js
55
+ - app/assets/stylesheets/tips.css
56
+ - lib/tips.rb
57
+ - lib/tips/engine.rb
58
+ - lib/tips/version.rb
59
+ - tips.gemspec
60
+ homepage: ''
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.4.1
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Slack inspired onboarding tips
84
+ test_files: []