tips 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []