Full Code of juliancwirko/meteor-s-alert for AI

master adf8cee11469 cached
11 files
48.3 KB
12.7k tokens
1 requests
Download .txt
Repository: juliancwirko/meteor-s-alert
Branch: master
Commit: adf8cee11469
Files: 11
Total size: 48.3 KB

Directory structure:
gitextract_1e02wi4t/

├── .versions
├── CHANGELOG.md
├── README.md
├── client/
│   ├── s-alert-collection.js
│   ├── s-alert-default.css
│   ├── s-alert-template.html
│   ├── s-alert-template.js
│   └── s-alert.js
├── package.js
├── tests/
│   └── s-alert-test.js
└── versions.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .versions
================================================
base64@1.0.3
binary-heap@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
callback-hook@1.0.3
check@1.0.5
ddp@1.1.0
deps@1.0.7
ejson@1.0.6
geojson-utils@1.0.3
html-tools@1.0.4
htmljs@1.0.4
id-map@1.0.3
jquery@1.11.3_2
json@1.0.3
juliancwirko:s-alert@1.1.2
logging@1.0.7
meteor@1.1.6
minifiers@1.1.5
minimongo@1.0.8
mongo@1.1.0
observe-sequence@1.0.6
ordered-dict@1.0.3
random@1.0.3
reactive-var@1.0.5
retry@1.0.3
spacebars-compiler@1.0.6
templating@1.1.1
tracker@1.0.7
ui@1.0.6
underscore@1.0.3


================================================
FILE: CHANGELOG.md
================================================
### Changelog
#### v3.2.0
- ready for Meteor 1.4 [#56](https://github.com/juliancwirko/meteor-s-alert/pull/56)

#### v3.1.4
- small fix - data check added [#46](https://github.com/juliancwirko/meteor-s-alert/issues/46)

#### v3.1.3
- onClose callback added. Many thanks to [@szchenghuang](https://github.com/szchenghuang) [#44](https://github.com/juliancwirko/meteor-s-alert/pull/44)

#### v3.1.2
- separation of positions logic - there was a problems when you wanted use many alerts positions at the same time [#35](https://github.com/juliancwirko/meteor-s-alert/issues/35)

#### v3.1.1
- stack up limit improvements [#31](https://github.com/juliancwirko/meteor-s-alert/issues/31)

#### v3.1.0
- clear audio on close by hand [#25](https://github.com/juliancwirko/meteor-s-alert/issues/25)
- stack up spacing & limit [#31](https://github.com/juliancwirko/meteor-s-alert/issues/31)

#### v3.0.0
- old API cleanup
- Audio alerts

#### v2.4.2
- Error object parse fix [#29](https://github.com/juliancwirko/meteor-s-alert/issues/29)

#### v2.4.1
- Router clearing option - Flow Router new triggers API adjustments

#### v2.4.0
- `stack` param - enable/disable stacking feature
- `offset` param - top or bottom offset of the first alert on screen. In pixels. Default '0'

#### v2.3.5
- `onRouteClose` param - you can decide whether you want to close your alerts (or particular ones) when your route changes (default true)

#### v2.3.4
- Some fixes related to template overwrite

#### v2.3.2, v2.3.3
- Some tests added

#### v2.3.1
- Iron Router clear fix

#### v2.3.0
- displaying more than one alert ([#7](https://github.com/juliancwirko/meteor-s-alert/issues/7))

#### v2.2.0
- now you can use HTML in your messages (thanks to [@gibex](https://github.com/gibex))

#### v2.1.1
- console info fix ([#12](https://github.com/juliancwirko/meteor-s-alert/issues/12))
- sAlert init functions now returns alert id ([#15](https://github.com/juliancwirko/meteor-s-alert/issues/15))

#### v2.1.0
- Postition names changed; example: 'right-bottom' is now 'bottom-right' etc. (The old names will work too, for backwards compatibility, but will be removed in v3.0.0.)
- CSS class names changed; example: '.s-alert-blue' is now '.s-alert-info', coresponding to sAlert.info(...) etc.
- two new positions: 'top' and 'bottom', for full-width alerts; they work for all effects
- timeout 'no' is now 'none' ('no' is deprecated and will work but will be removed in v3.0.0)

#### v2.0.0
- factor out effects into separate packages

================================================
FILE: README.md
================================================
### Simple and fancy notifications for Meteor

- Website: [http://s-alert.meteorapp.com/](http://s-alert.meteorapp.com/)
- Demo: [http://s-alert-demo.meteorapp.com/](http://s-alert-demo.meteorapp.com/)
- [s-Alert on the Meteor Podcast](http://www.meteorpodcast.com/e/episode-60-the-club-edition/)
- [Pure React UI Component](https://www.npmjs.com/package/react-s-alert)

### sAlert Usage

Add package:

    meteor add juliancwirko:s-alert

Optionally, add one or more effects:

    meteor add juliancwirko:s-alert-scale
    meteor add juliancwirko:s-alert-slide
    meteor add juliancwirko:s-alert-genie
    meteor add juliancwirko:s-alert-jelly
    meteor add juliancwirko:s-alert-flip
    meteor add juliancwirko:s-alert-bouncyflip
    meteor add juliancwirko:s-alert-stackslide

Then place `{{> sAlert}}` in your main template. Recomended usage:

```handlebars
<body>
    {{> sAlert}}
</body>
```

#### sAlert configuration

sAlert can be configured on the client, be sure to put this inside the /client directory because running it on the server will cause it to crash (more about possible configuration options below). The defaults are below:

```js
Meteor.startup(function () {

    sAlert.config({
        effect: '',
        position: 'top-right',
        timeout: 5000,
        html: false,
        onRouteClose: true,
        stack: true,
        // or you can pass an object:
        // stack: {
        //     spacing: 10 // in px
        //     limit: 3 // when fourth alert appears all previous ones are cleared
        // }
        offset: 0, // in px - will be added to first alert (bottom or top - depends of the position in config)
        beep: false,
        // examples:
        // beep: '/beep.mp3'  // or you can pass an object:
        // beep: {
        //     info: '/beep-info.mp3',
        //     error: '/beep-error.mp3',
        //     success: '/beep-success.mp3',
        //     warning: '/beep-warning.mp3'
        // }
        onClose: _.noop //
        // examples:
        // onClose: function() {
        //     /* Code here will be executed once the alert closes. */
        // }
    });

});
```

sAlert is based on a [client-only collection](http://docs.meteor.com/#/full/mongo_collection). It is called `sAlert.collection`. Every sAlert method returns the ID of the alert it has just created.

```js
var warningThatWeWantToCloseLater = sAlert.warning('Please register', {timeout: 'none'});
/* ... */
sAlert.close(warningThatWeWantToCloseLater);
```

#### Fire up your alerts with these methods:

##### Error

    sAlert.error('Your message', configOverwrite);

##### Warning

    sAlert.warning('Your message', configOverwrite);

##### Info

    sAlert.info('Your message', configOverwrite);

##### Success

    sAlert.success('Your message', configOverwrite);

##### Close alert:

    sAlert.close(alertId);

##### Immediately close all alerts:

    sAlert.closeAll();

#### Individual alert configuration

And what is `configOverwrite`? This is an object with all the settings you want to override, on a per-alert basis. For example:

```js
sAlert.error('Boom! Something went wrong!', {effect: 'genie', position: 'bottom-right', timeout: 'none', onRouteClose: false, stack: false, offset: '80px'});
```

This particular error will be displayed in different way.

#### Available effects:

- scale - `meteor add juliancwirko:s-alert-scale`
- slide - `meteor add juliancwirko:s-alert-slide`
- genie - `meteor add juliancwirko:s-alert-genie`
- jelly - `meteor add juliancwirko:s-alert-jelly`
- flip - `meteor add juliancwirko:s-alert-flip`
- bouncyflip - `meteor add juliancwirko:s-alert-bouncyflip`
- stackslide - `meteor add juliancwirko:s-alert-stackslide`

#### Available positions:

- top-left
- bottom-left
- top-right
- bottom-right
- top (full width)
- bottom (full width)

#### Timeout:

You can set up it in miliseconds or use the string `none`.

#### Callback onClose:

You can hook a callback to be invoked when the alert closes. This callback will be invoked when the default timeout closes the alert.

```js
sAlert.success('Your message', {onClose: function() {console.log('closing alert...');}});
```

If `timeout` is set specifically, it respects the setting.

```js
sAlert.success('Your message', {timeout: 1000, onClose: function() {console.log('closing alert in 1000ms...');}});
```

The callback will also be invoked if you specifically close the alert.

```js
var sAlertId = sAlert.success('Your message', {onClose: function() {console.log('closing alert...');}});
sAlert.close(sAlertId);
```

It applies for `closeAll` as well.

```js
sAlert.success('Your message one', {onClose: function() {console.log('closing alert one...');}});
sAlert.success('Your message two', {onClose: function() {console.log('closing alert two...');}});
sAlert.closeAll();
```

#### HTML tags

If you want you can use HTML in your message.

```js
sAlert.error('Boom! <br> Something went wrong!', {effect: 'your-effect-name-here', html: true});
```
You can also put it in the main sAlert config.

#### Closing alerts on route change

If you go to another route, in default the alerts should automatically be cleaned up. This works with Iron Router and FlowRouter. However if you want the alerts to persists on route change you should change `onRouteClose` param in your config (example above).

You can even overwrite it in sAlert methods calls. So you can close only some of the alerts on route change. Example:

```javascript
sAlert.warning('Opssss!!! No good! Keep me even when the route changes.', {onRouteClose: false, timeout: 10000});
sAlert.info('Be careful and hide me when the route changes.', {onRouteClose: true, timeout: 10000});
```

#### Stacking alerts

By default your multiple alerts on the screen will appear one after another with shift on top or bottom. You can disable it by stack param. Just set it to false.

```javascript
sAlert.info('Opssss!!! I am full width alert without stacking enabled', {position: 'top'; stack: false});
```
You can also put it in the main sAlert config.

There is an option to set up alerts limit on page and spacing between them.
Sometimes when you use long timeouts (or no timeouts) it is better to use configured limit. So when it will be reached all previous alerts will be cleared immediately.
Instead of using `stack: true` you can pass an object like:

```
...
stack: {
    spacing: 10, // in px
    limit: 3
}
...
```

See full config above.

Remember that if you use `stack.spacing` configuration you probably might want to use offset too, because the first alert will always have 0px spacing from top or bottom. If you use only `stack: true` there will be standard 30px spacing between alerts.

#### Alerts offset

If you want you can set up offset for your alerts. This is useful when you have for example some header and you want your alerts to appear below it. You can set this param in pixels. Default is '0';

```javascript
sAlert.info('Opssss!!! I am displayed below the header which is 70px height', {position: 'top'; offset: '100px'});
```
You can also put it in the main sAlert config.

#### Audio alerts

You can set up your audio 'beeps'. Just configure your audio file path (.mp3 is prefered because it should work in every browser). You can also configure 4 paths for 4 conditions. The best way is to put your audio files in `public` folde. Check the configuration above for more details.

**There is no default audio sample in the package.** You should use sound samples which you know that you have the right to use it.

### CSS styling

You can override all CSS classes by targeting `s-alert-{{alertType}}.s-alert-effect-{{effectType}}`. The alert type classes are:

    .s-alert-info, .s-alert-success, .s-alert-warning, .s-alert-error

For example, this CSS rule will override the style for `.s-alert-error` when displayed with the `scale` effect:

```css
.s-alert-error.s-alert-effect-scale {
    background: #bada55;  /* your background color here */
    color: #fff  /* your text color here */
}
```

### Your own effects packages

You can prepare your own effect package. As a reference, look at one of the ready-to-use packages, such as [meteor-s-alert-jelly](https://github.com/juliancwirko/meteor-s-alert-jelly). You can create your own animations, but remember to use the `.s-alert-effect-{your-effect-name-here}` prefix. Then you can use it like:

```js
sAlert.error('Boom! Something went wrong!', {effect: 'your-effect-name-here'});
```

Or you can place it in the config:

```js
Meteor.startup(function () {

    sAlert.config({
        effect: 'your-effect-name-here',
        position: 'top-right',
        timeout: 5000
    });

});
```

If you want to have your effect package linked here just let me know.

### Template overwriting

Here is a default template (it will be included when you use the standard `{{> sAlert}}`):

```handlebars
<div class="s-alert-box s-alert-{{condition}} s-alert-{{position}} {{#if effect}}s-alert-is-effect s-alert-effect-{{effect}}{{/if}} s-alert-show" id="{{_id}}" style="{{boxPosition}}">
    <div class="s-alert-box-inner">
        <p>{{message}}</p>
    </div>
    <span class="s-alert-close"></span>
</div>
```

If you want to owerwrite it you should remember to be careful with all used helpers. They should remain in place.
**Here you have an example of overwriting an alert content template** (Place it somewhere in your html files, you can name it as you want):

```handlebars
<template name="sAlertCustom">
    <div class="custom-alert-class s-alert-box s-alert-{{condition}} s-alert-{{position}} {{#if effect}}s-alert-is-effect s-alert-effect-{{effect}}{{/if}} s-alert-show" id="{{_id}}" style="{{boxPosition}}">
        <div class="s-alert-box-inner">
            <div class="alert-header">
                <h1><i class="fa fa-{{sAlertIcon}}"></i> {{sAlertTitle}}</h1>
            </div>
            <div class="alert-content">
                <i class="fa fa-fw fa-cog"></i>
                {{message}}
            </div>
        </div>
        <span class="s-alert-close"></span>
    </div>
</template>
```

#### Usage of custom template

Place `{{> sAlert template='sAlertCustom'}}` in your main template.

#### Custom fields

As you can see in a custom `sAlertCustom` template we have used the `sAlertTitle` custom helper. Now if you want to pass the value to it you should call one of sAlert functions with the first parameter being an object instead of a message string:

```js
sAlert.info({sAlertIcon: 'asterisk', sAlertTitle: 'My custom sAlert field - the title', message: 'My sAlert message here'}, configOverwrite);
```

You can pass as many fields as you like. Remember to add the corresponding helpers in the template. `configOverwrite` works here the same as described above. It is of course optional.

#### Testing

Clone it into `packages` folder and run meteor with:
```
meteor test-packages --driver-package practicalmeteor:mocha juliancwirko:s-alert
```

and go to:

```
http://localhost:3000
```

- - -

#### Inspiration:

- [Codrops Article - Notification Styles Inspiration](http://tympanus.net/codrops/2014/07/23/notification-styles-inspiration/)

Thanks a lot for those who report bugs and request changes (especially [@dandv](https://github.com/dandv)). sAlert keeps getting better.

#### Also check out:

- [React with Webpack + Meteor as a backend only](http://julian.io/react-with-webpack-meteor-as-a-backend/)
- [s-alert for React](https://www.npmjs.com/package/react-s-alert)
- [sGrid](https://atmospherejs.com/juliancwirko/s-grid)
- [sId](https://atmospherejs.com/juliancwirko/s-id)
- [sImageBox](https://atmospherejs.com/juliancwirko/s-image-box)
- [sChat - Open Source Live Chat App](https://www.simplechat.support)
- [Scotty Boilerplate](https://github.com/juliancwirko/scotty)
- [PostCSS for Meteor](https://atmospherejs.com/juliancwirko/postcss)

**Note: Starting with version 3.0.0 old deprecated APIs are removed**

**Note: Starting with version 2.0.0 you should also choose and add and effect package.**
This is a more flexible and lean solution (previously, the effects CSS file contained all effect styles and it was heavy). sAlert will work without effects as well. You can add as many effect packages as you want. Config and usage are the same.

#### Changelog

..see [CHANGELOG.md](https://github.com/juliancwirko/meteor-s-alert/blob/master/CHANGELOG.md) file

#### License
MIT


================================================
FILE: client/s-alert-collection.js
================================================
'use strict';

// only client side collections for now..
sAlert.collection = new Mongo.Collection(null);


================================================
FILE: client/s-alert-default.css
================================================
/* Common, default styles for the notification box */

.s-alert-box,
.s-alert-box * {
	box-sizing: border-box;
}

.s-alert-box {
	position: fixed;
	background: rgba(42,45,50,0.85);
	padding: 22px;
	line-height: 1.4;
	z-index: 1000;
	pointer-events: none;
	color: rgba(250,251,255,0.95);
	font-size: 100%;
	font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif;
	max-width: 300px;
	-webkit-transition: top .4s, bottom .4s;
	transition: top .4s, bottom .4s;
}

.s-alert-box.s-alert-show {
	pointer-events: auto;
}

.s-alert-box a {
	color: inherit;
	opacity: 0.7;
	font-weight: 700;
}

.s-alert-box a:hover,
.s-alert-box a:focus {
	opacity: 1;
}

.s-alert-box p {
	margin: 0;
}

.s-alert-box.s-alert-show,
.s-alert-box.s-alert-visible {
	pointer-events: auto;
}

.s-alert-close {
	width: 20px;
	height: 20px;
	position: absolute;
	right: 4px;
	top: 4px;
	overflow: hidden;
	text-indent: 100%;
	cursor: pointer;
	-webkit-backface-visibility: hidden;
	backface-visibility: hidden;
}

.s-alert-close:hover,
.s-alert-close:focus {
	outline: none;
}

.s-alert-close::before,
.s-alert-close::after {
	content: '';
	position: absolute;
	width: 3px;
	height: 60%;
	top: 50%;
	left: 50%;
	background: #fff;
}

.s-alert-close:hover::before,
.s-alert-close:hover::after {
	background: #fff;
}

.s-alert-close::before {
	-webkit-transform: translate(-50%,-50%) rotate(45deg);
	transform: translate(-50%,-50%) rotate(45deg);
}

.s-alert-close::after {
	-webkit-transform: translate(-50%,-50%) rotate(-45deg);
	transform: translate(-50%,-50%) rotate(-45deg);
}

/* positions */

.s-alert-bottom-left {
	top: auto;
	right: auto;
	bottom: 30px;
	left: 30px;
}
.s-alert-top-left {
	top: 30px;
	right: auto;
	bottom: auto;
	left: 30px;
}
.s-alert-top-right {
	top: 30px;
	right: 30px;
	bottom: auto;
	left: auto;
}
.s-alert-bottom-right { /*default*/
	top: auto;
	right: 30px;
	bottom: 30px;
	left: auto;
}
.s-alert-bottom {
	width: 100%;
	max-width: 100%;
	bottom: 0;
	left: 0;
	right: 0;
	top: auto;
}
.s-alert-top {
	width: 100%;
	max-width: 100%;
	top: 0;
	left: 0;
	right: 0;
	bottom: auto;
}

/* conditions */

.s-alert-info {
    background: #00A2D3;
    color: #fff;
}
.s-alert-success {
    background: #27AE60;
    color: #fff;
}
.s-alert-warning {
    background: #F1C40F;
    color: #fff;
}
.s-alert-error {
    background: #E74C3C;
    color: #fff;
}

[class^="s-alert-effect-"].s-alert-hide,
[class*=" s-alert-effect-"].s-alert-hide {
    -webkit-animation-direction: reverse;
    animation-direction: reverse;
}

/* height measurement helper */
.s-alert-box-height {
	visibility: hidden;
	position: fixed;
}

================================================
FILE: client/s-alert-template.html
================================================
<template name="sAlert">
    {{#each sAlertDataLeft}}
        {{> sAlertContent}}
    {{/each}}
    {{#each sAlertDataRight}}
        {{> sAlertContent}}
    {{/each}}
    {{#each sAlertDataFullTop}}
        {{> sAlertContent}}
    {{/each}}
    {{#each sAlertDataFullBottom}}
        {{> sAlertContent}}
    {{/each}}
</template>

<template name="sAlertContent">
    {{#if ../template}}
        {{> Template.dynamic template=../template data=this}}
    {{else}}
        <div class="s-alert-box s-alert-{{condition}} s-alert-{{position}} {{#if effect}}s-alert-is-effect s-alert-effect-{{effect}}{{/if}} s-alert-show" id="{{_id}}" style="{{boxPosition}}">
            <div class="s-alert-box-inner">
                <p>{{#if isHtml}}{{{message}}}{{else}}{{message}}{{/if}}</p>
            </div>
            <span class="s-alert-close"></span>
        </div>
    {{/if}}
</template>

================================================
FILE: client/s-alert-template.js
================================================
'use strict';

var getAlertData = function (currentData, sAlertPosition) {
    var positionTop = 0;
    var positionBottom = 0;
    var padding = 0;
    var alerts = {};
    var style;
    var sAlertBoxHTML;
    var sAlertBox;
    var docElement;
    var sAlertBoxHeight;
    var templateOverwrite = currentData && currentData.template;
    var positionTypeTop;
    var positionTypeBottom;
    var stackLimit;
    var alertsCount;
    var checkFirst = function (type, objId) {
        var collectionOfType = sAlertCollection.filter(function(obj) {
            return obj.position === type;
        });
        return collectionOfType && collectionOfType[0]._id === objId;
    };
    var positionFunc = function (position, positionType, alert, sAlertBox) {
        padding = alert.stack.spacing || sAlertBox.find('.s-alert-box').css(positionType);
        if (checkFirst(alert.position, alert._id) && alert.offset) {
            position = 0;
            position = position + parseInt(alert.offset);
        }
        if (checkFirst(alert.position, alert._id) && alert.stack.spacing) {
            position = position;
        } else {
            position = position + parseInt(padding);
        }
        style = positionType + ': ' + position + 'px;';
        position = position + sAlertBoxHeight;
        return position;
    };

    var query = {};
    if (sAlertPosition === 'left') {
        query = {$or: [{position: 'top-left'}, {position: 'bottom-left'}]};
    }
    if (sAlertPosition === 'right') {
        query = {$or: [{position: 'top-right'}, {position: 'bottom-right'}]};
    }
    if (sAlertPosition === 'full-top') {
        query = {position: 'top'};
    }
    if (sAlertPosition === 'full-bottom') {
        query = {position: 'bottom'};
    }
    var sAlertCollection = sAlert.collection.find(query).fetch();

    return sAlertCollection.map(function (alert) {
        positionTypeTop = alert.position && /top/g.test(alert.position);
        positionTypeBottom = alert.position && /bottom/g.test(alert.position);
        if (alert.stack) {
            stackLimit = alert.stack && alert.stack.limit;
            alertsCount = sAlert.collection.find(query).count();
            // limit check
            if (stackLimit && alertsCount > stackLimit) {
                sAlert.close(sAlert.collection.findOne(query)._id);
            }
            // checking alert box height - needed to calculate position
            docElement = document.createElement('div');
            $(docElement).addClass('s-alert-box-height');
            if (_.isString(templateOverwrite)) {
                sAlertBoxHTML = Blaze.toHTMLWithData(Template[templateOverwrite], alert);
            } else {
                sAlertBoxHTML = Blaze.toHTMLWithData(Template.sAlertContent, alert);
            }
            sAlertBox = $(docElement).html(sAlertBoxHTML);
            $('body').append(sAlertBox);
            sAlertBoxHeight = sAlertBox.find('.s-alert-box').outerHeight(true);
            if (positionTypeTop) {
                positionTop = positionFunc(positionTop, 'top', alert, sAlertBox);
            }
            if (positionTypeBottom) {
                positionBottom = positionFunc(positionBottom, 'bottom', alert, sAlertBox);
            }
            sAlertBox.remove();
            if (sAlertPosition === 'left') {
                style = style + 'left: ' + (alert.stack.spacing || sAlertBox.find('.s-alert-box').css('left')) + 'px;';
            }
            if (sAlertPosition === 'right') {
                style = style + 'right: ' + (alert.stack.spacing || sAlertBox.find('.s-alert-box').css('right')) + 'px;';
            }
            alerts = _.extend(alert, {boxPosition: style});
        } else if (alert.offset && positionTypeTop) {
            alerts = _.extend(alert, {boxPosition: 'top: ' + parseInt(alert.offset) + 'px;'});
        } else if (alert.offset && positionTypeBottom) {
            alerts = _.extend(alert, {boxPosition: 'bottom: ' + parseInt(alert.offset) + 'px;'});
        } else {
            alerts = alert;
        }
        return alerts;
    });
};

Template.sAlert.helpers({
    sAlertDataLeft: function () {
        return getAlertData(Template.currentData(), 'left');
    },
    sAlertDataRight: function () {
        return getAlertData(Template.currentData(), 'right');
    },
    sAlertDataFullTop: function () {
        return getAlertData(Template.currentData(), 'full-top');
    },
    sAlertDataFullBottom: function () {
        return getAlertData(Template.currentData(), 'full-bottom');
    }
});

Template.sAlertContent.onRendered(function () {
    var tmpl = this;
    var data = Template.currentData();
    var sAlertTimeout = data.timeout;
    var beep = data.beep;
    // audio
    if (beep && _.isString(beep)) {
        sAlert.audio = new Audio(data.beep);
        sAlert.audio.load();
        sAlert.audio.play();
    }
    if (beep && _.isObject(beep) && data.condition === 'info') {
        sAlert.audioInfo = new Audio(data.beep.info);
        sAlert.audioInfo.load();
        sAlert.audioInfo.play();
    }
    if (beep && _.isObject(beep) && data.condition === 'error') {
        sAlert.audioError = new Audio(data.beep.error);
        sAlert.audioError.load();
        sAlert.audioError.play();
    }
    if (beep && _.isObject(beep) && data.condition === 'success') {
        sAlert.audioSuccess = new Audio(data.beep.success);
        sAlert.audioSuccess.load();
        sAlert.audioSuccess.play();
    }
    if (beep && _.isObject(beep) && data.condition === 'warning') {
        sAlert.audioWarning = new Audio(data.beep.warning);
        sAlert.audioWarning.load();
        sAlert.audioWarning.play();
    }
    if (sAlertTimeout && sAlertTimeout !== 'none') {
        sAlertTimeout = parseInt(sAlertTimeout);
        if (tmpl.sAlertCloseTimeout) {
            Meteor.clearTimeout(tmpl.sAlertCloseTimeout);
        }
        tmpl.sAlertCloseTimeout = Meteor.setTimeout(function () {
            sAlert.close(data._id);
        }, sAlertTimeout);
    }
});
Template.sAlertContent.onDestroyed(function () {
    if (this.sAlertCloseTimeout) {
        Meteor.clearTimeout(this.sAlertCloseTimeout);
    }
});

Template.sAlertContent.events({
    'click .s-alert-close': function (e, tmpl) {
        e.preventDefault();
        Meteor.clearTimeout(tmpl.sAlertCloseTimeout);
        sAlert.close(this._id);
    }
});

Template.sAlertContent.helpers({
    isHtml: function () {
        var data = Template.currentData();
        return data && data.html;
    }
});


================================================
FILE: client/s-alert.js
================================================
'use strict';

// helper functions
var conditionSet = function (self, msg, condition, customSettings) {
    var settings = {};
    var effects = ['jelly', 'genie', 'stackslide', 'scale', 'slide', 'flip', 'bouncyflip'];
    var currentEffect;
    var sAlertId;
    if (!_.isObject(customSettings)) {
        customSettings = {};
    }
    if (_.isObject(msg) && _.isString(condition)) {
        settings = _.extend(settings, self.settings, JSON.parse(JSON.stringify(msg)), {condition: condition}, customSettings);
    }
    if (_.isString(msg) && _.isString(condition)) {
        settings = _.extend(settings, self.settings, {message: msg}, {condition: condition}, customSettings);
    }
    currentEffect = settings && settings.effect;
    if (_.contains(effects, currentEffect) && !Package['juliancwirko:s-alert-' + currentEffect] && typeof console !== 'undefined') {
        console.info('Install "' + currentEffect + '" effect by running "meteor add juliancwirko:s-alert-' + currentEffect + '"');
    }
    if (_.isObject(settings) && !_.isEmpty(settings)) {
        sAlertId = sAlert.collection.insert(settings);
    }
    return sAlertId;
};

var EVENTS = 'webkitAnimationEnd oAnimationEnd animationEnd msAnimationEnd animationend';
var sAlertClose = function (alertId) {
    var closingTimeout;
    var onClose;
    var alertObj;
    var invokeOnCloseCb = function () {
        // invoke onClose callback
        if (onClose && _.isFunction(onClose)) {
            onClose();
        }
    };
    if (document.hidden || document.webkitHidden || !$('#' + alertId).hasClass('s-alert-is-effect')) {
        alertObj = sAlert.collection.findOne(alertId);
        if (alertObj && !_.isEmpty(alertObj)) {
            onClose = alertObj.onClose;
        }
        sAlert.collection.remove(alertId);
        invokeOnCloseCb();
    } else {
        $('.s-alert-box#' + alertId).removeClass('s-alert-show');
        closingTimeout = Meteor.setTimeout(function () {
            $('.s-alert-box#' + alertId).addClass('s-alert-hide');
        }, 100);
        $('.s-alert-box#' + alertId).off(EVENTS);
        $('.s-alert-box#' + alertId).on(EVENTS, function () {
            $(this).hide();
            alertObj = sAlert.collection.findOne(alertId);
            if (alertObj && !_.isEmpty(alertObj)) {
                onClose = alertObj.onClose;
            }
            sAlert.collection.remove(alertId);
            Meteor.clearTimeout(closingTimeout);
            invokeOnCloseCb();
        });
    }
    // stop audio when closing
    sAlert.audio && sAlert.audio.load();
    sAlert.audioInfo && sAlert.audioInfo.load();
    sAlert.audioError && sAlert.audioError.load();
    sAlert.audioSuccess && sAlert.audioSuccess.load();
    sAlert.audioWarning && sAlert.audioWarning.load();
};

// sAlert object
sAlert = {
    settings: {
        effect: '',
        position: 'top-right',
        timeout: 5000,
        html: false,
        onRouteClose: true,
        stack: true,
        // or you can pass an object:
        // stack: {
        //     spacing: 10 // in px
        //     limit: 3 // when fourth alert appears all previous ones are cleared
        // }
        offset: 0, // in px - will be added to first alert (bottom or top - depends of the position in config)
        beep: false,
        // beep: '/beep.mp3'  // or you can pass an object:
        // beep: {
        //     info: '/beep-info.mp3',
        //     error: '/beep-error.mp3',
        //     success: '/beep-success.mp3',
        //     warning: '/beep-warning.mp3'
        // }
        onClose: _.noop
    },
    config: function (configObj) {
        var self = this;
        if (_.isObject(configObj)) {
            self.settings = _.extend(self.settings, configObj);
        } else {
            throw new Meteor.Error(400, 'Config must be an object!');
        }
    },
    closeAll: function () {
        sAlert.collection.find({}).forEach(function (sAlertObj) {
            sAlert.collection.remove(sAlertObj._id);
            if (sAlertObj.onClose && _.isFunction(sAlertObj.onClose)) {
                sAlertObj.onClose();
            }
        });
    },
    close: function (id) {
        if (_.isString(id)) {
            sAlertClose(id);
        }
    },
    info: function (msg, customSettings) {
        return conditionSet(this, msg, 'info', customSettings);
    },
    error: function (msg, customSettings) {
        return conditionSet(this, msg, 'error', customSettings);
    },
    success: function (msg, customSettings) {
        return conditionSet(this, msg, 'success', customSettings);
    },
    warning: function (msg, customSettings) {
        return conditionSet(this, msg, 'warning', customSettings);
    }
};

// routers clean
Meteor.startup(function () {
    if (typeof Iron !== 'undefined' && typeof Router !== 'undefined') {
        Router.onStop(function () {
            sAlert.collection.remove({onRouteClose: true});
        });
    }
    if (typeof FlowRouter !== 'undefined' && _.isObject(FlowRouter.triggers)) {
        FlowRouter.triggers.enter([function () {
            sAlert.collection.remove({onRouteClose: true});
        }]);
    }
    if (typeof FlowRouter !== 'undefined' && !_.isObject(FlowRouter.triggers)) {
        FlowRouter.middleware(function (path, next) {
            sAlert.collection.remove({onRouteClose: true});
            next();
        });
    }
});


================================================
FILE: package.js
================================================
Package.describe({
    'summary': 'Simple and fancy notifications / alerts / errors for Meteor',
    'version': '3.2.0',
    'git': 'https://github.com/juliancwirko/meteor-s-alert.git',
    'name': 'juliancwirko:s-alert'
});

Package.onUse(function (api) {
    api.versionsFrom('METEOR@1.1.0.3');
    api.use('mongo');
    api.use('templating');
    api.use('ui');
    api.use('underscore');
    api.use(['jquery', 'session'], ['client']);
    api.addFiles([
        'client/s-alert.js',
        'client/s-alert-collection.js',
        'client/s-alert-default.css',
        'client/s-alert-template.html',
        'client/s-alert-template.js'
    ], 'client');
    api.export('sAlert', ['client']);
});

Package.onTest(function (api) {
    api.versionsFrom('METEOR@1.1.0.3');
    api.use(['jquery'], ['client']);
    api.use([
        'templating',
        'juliancwirko:s-alert@3.2.0',
        'practicalmeteor:mocha@2.4.5_6',
        'practicalmeteor:chai@2.1.0_1'
    ]);
    api.addFiles(['tests/s-alert-test.js'], 'client');
});



================================================
FILE: tests/s-alert-test.js
================================================
'use strict';

var sAlertRender = function () {
    var body = document.getElementsByTagName('body')[0];
    return Blaze.render(Template.sAlert, body);
};

var getCSSProperty = function (selector, property) {
    return getComputedStyle(document.querySelector(selector), null).getPropertyValue(property);
};

describe('sAlert warning function', function () {
    var renderedView;
    before(function () {
        sAlert.warning('Test warning message...', {timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should be s-alert warning document in the sAlert.collection', function () {
        chai.expect(sAlert.collection.findOne().message).to.equal('Test warning message...');
        chai.expect(sAlert.collection.findOne().condition).to.equal('warning');
    });
    it('should be ".s-alert-warning" element in the DOM', function () {
        chai.expect($('.s-alert-box').length).to.not.equal(0);
        chai.expect($('.s-alert-box').hasClass('s-alert-warning')).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert success function', function () {
    var renderedView;
    before(function () {
        sAlert.success('Test success message...', {timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should be s-alert success document in the sAlert.collection', function () {
        chai.expect(sAlert.collection.findOne().message).to.equal('Test success message...');
        chai.expect(sAlert.collection.findOne().condition).to.equal('success');
    });
    it('should be ".s-alert-success" element in the DOM', function () {
        chai.expect($('.s-alert-box').length).to.not.equal(0);
        chai.expect($('.s-alert-box').hasClass('s-alert-success')).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert info function', function () {
    var renderedView;
    before(function () {
        sAlert.info('Test info message...', {timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should be s-alert info document in the sAlert.collection', function () {
        chai.expect(sAlert.collection.findOne().message).to.equal('Test info message...');
        chai.expect(sAlert.collection.findOne().condition).to.equal('info');
    });
    it('should be ".s-alert-info" element in the DOM', function () {
        chai.expect($('.s-alert-box').length).to.not.equal(0);
        chai.expect($('.s-alert-box').hasClass('s-alert-info')).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert error function', function () {
    var renderedView;
    before(function () {
        sAlert.error('Test error message...', {timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should be s-alert error document in the sAlert.collection', function () {
        chai.expect(sAlert.collection.findOne().message).to.equal('Test error message...');
        chai.expect(sAlert.collection.findOne().condition).to.equal('error');
    });
    it('should be ".s-alert-error" element in the DOM', function () {
        chai.expect($('.s-alert-box').length).to.not.equal(0);
        chai.expect($('.s-alert-box').hasClass('s-alert-error')).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert close function by alert id', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test close function...', {timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should be s-alert document and element in DOM', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).length).to.not.equal(0);
        chai.expect($('.s-alert-box').length).to.not.equal(0);
    });
    it('should be no s-alert document in the collection after sAlert.close function is called', function () {
        sAlert.close(sAlertId);
        chai.expect(sAlert.collection.findOne(sAlertId)).to.be.undefined;
    });
    it('should be no s-alert element in the DOM after sAlert.close function is called', function () {
        Blaze.remove(renderedView);
        chai.expect($('.s-alert-box').length).to.equal(0);
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert 1000ms timeout', function () {
    var renderedView;
    var sAlertId;
    before(function (done) {
        sAlertId = sAlert.success('Test timeout param...', {timeout: 1000});
        renderedView = sAlertRender();
        Meteor.setTimeout(function () {
            done();
        }, 1500);
    });
    it('should not be s-alert document in the collection after 1500ms', function (done) {
        chai.expect(sAlert.collection.findOne(sAlertId)).to.be.undefined;
        done();
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert 1800ms timeout', function () {
    var renderedView;
    var sAlertId;
    before(function (done) {
        sAlertId = sAlert.success('Test timeout param...', {timeout: 1800});
        renderedView = sAlertRender();
        Meteor.setTimeout(function () {
            done();
        }, 1000);
    });
    it('should be s-alert document in the collection after 1000ms', function (done) {
        chai.expect(sAlert.collection.findOne(sAlertId)).to.not.equal(0);
        done();
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position bottom-left', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'bottom-left', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-bottom-left class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-bottom-left')).to.be.true;
    });
    it('should have document with position bottom-left in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('bottom-left');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position top-left', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'top-left', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-top-left class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-top-left')).to.be.true;
    });
    it('should have document with position top-left in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('top-left');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position top-right', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'top-right', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-top-right class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-top-right')).to.be.true;
    });
    it('should have document with position top-right in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('top-right');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position bottom-right', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'bottom-right', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-bottom-right class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-bottom-right')).to.be.true;
    });
    it('should have document with position bottom-right in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('bottom-right');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position bottom', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'bottom', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-bottom class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-bottom')).to.be.true;
    });
    it('should have document with position bottom in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('bottom');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert position top', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'top', timeout: 'none'});
        renderedView = sAlertRender();
    });
    it('should have s-alert-top class', function () {
        chai.expect($('.s-alert-box').hasClass('s-alert-top')).to.be.true;
    });
    it('should have document with position top in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).position).to.equal('top');
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert offset', function () {
    var renderedView;
    var sAlertId;
    before(function () {
        sAlertId = sAlert.success('Test position...', {position: 'top-right', timeout: 'none', offset: 100});
        renderedView = sAlertRender();
    });
    // TODO : test needs to be changed
    // it('should have top offset set', function () {
    //     chai.expect(getCSSProperty('.s-alert-box', 'top')).to.equal(100);
    // });
    it('should have document with offset in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId).offset).to.equal(100);
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert without stacking', function () {
    var renderedView1;
    var renderedView2;
    var sAlertId1;
    var sAlertId2;
    var sa1;
    var sa2;
    before(function () {
        sAlertId1 = sAlert.success('Test position...', {position: 'top', timeout: 'none', stack: false});
        renderedView1 = sAlertRender();
        sAlertId2 = sAlert.success('Test position...', {position: 'top', timeout: 'none', stack: false});
        renderedView2 = sAlertRender();
        sa1 = $('#' + sAlertId1).css('top');
        sa2 = $('#' + sAlertId2).css('top');
    });
    it('should have equal position as the previous one', function () {
        chai.expect(sa1).to.equal(sa2);
    });
    it('should have document with stack set to false in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId1).stack).to.be.false;
        chai.expect(sAlert.collection.findOne(sAlertId2).stack).to.be.false;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView1);
        Blaze.remove(renderedView2);
    });
});

describe('sAlert with stacking', function () {
    var renderedView1;
    var renderedView2;
    var sAlertId1;
    var sAlertId2;
    var sa1;
    var sa2;
    before(function () {
        sAlertId1 = sAlert.success('Test position...', {position: 'top', timeout: 'none', stack: true});
        renderedView1 = sAlertRender();
        sAlertId2 = sAlert.success('Test position...', {position: 'top', timeout: 'none', stack: true});
        renderedView2 = sAlertRender();
        sa1 = $('#' + sAlertId1).css('top');
        sa2 = $('#' + sAlertId2).css('top');
    });
    // TODO : test needs to be changed
    // it('should have not equal position as the previous one', function () {
    //     chai.expect(sa1).to.not.equal(sa2);
    // });
    it('should have document with stack set to true in the collection', function () {
        chai.expect(sAlert.collection.findOne(sAlertId1).stack).to.be.true;
        chai.expect(sAlert.collection.findOne(sAlertId2).stack).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView1);
        Blaze.remove(renderedView2);
    });
});

describe('sAlert callback onClose by close and closeAll functions', function () {
    var renderedView1;
    var renderedView2;
    var renderedView3;
    var sAlertId1;
    var sAlertId2;
    var sAlertId3;
    var isClosed1;
    var isClosed2;
    var isClosed3;
    before(function () {
        sAlertId1 = sAlert.success('Test onClose callback...', {timeout: 'none', onClose: function() {isClosed1 = true;}});
        renderedView1 = sAlertRender();
        sAlertId2 = sAlert.success('Test onClose callback...', {timeout: 'none', onClose: function() {isClosed2 = true;}});
        renderedView2 = sAlertRender();
        sAlertId3 = sAlert.success('Test onClose callback...', {timeout: 'none', onClose: function() {isClosed3 = true;}});
        renderedView3 = sAlertRender();
    });
    it('should get called when specifically closing the alert.', function () {
        sAlert.close(sAlertId1);
        chai.expect(isClosed1).to.be.true;
        chai.expect(isClosed2).to.be.undefined;
        chai.expect(isClosed3).to.be.undefined;
    });
    it('should get called when specifically closing all alerts..', function () {
        sAlert.closeAll();
        chai.expect(isClosed1).to.be.true;
        chai.expect(isClosed2).to.be.true;
        chai.expect(isClosed3).to.be.true;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView1);
        Blaze.remove(renderedView2);
        Blaze.remove(renderedView3);
    });
});

describe('sAlert callback onClose without timeout param', function () {
    var renderedView;
    var sAlertId;
    var isClosed;
    before(function (done) {
        this.timeout(sAlert.settings.timeout + 1000 );
        sAlertId = sAlert.success('Test onClose callback...', {onClose: function() {isClosed = true; done();}});
        renderedView = sAlertRender();
    });
    it('should get called when the default timeout closes the alert.', function (done) {
        chai.expect(isClosed).to.be.true;
        done();
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert onClose callback with timeout param', function () {
    var renderedView;
    var sAlertId;
    var isClosed;
    before(function () {
        sAlertId = sAlert.success('Test onClose callback...', {timeout: 'none', onClose: function() {isClosed = true;}});
        renderedView = sAlertRender();
    });
    it('should not get called with infinite timeout', function () {
        chai.expect(isClosed).to.be.undefined;
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert onClose callback with 1000ms timeout param', function () {
    var renderedView;
    var sAlertId;
    var isClosed;
    before(function (done) {
        sAlertId = sAlert.success('Test onClose callback...', {timeout: 1000, onClose: function() {isClosed = true;}});
        renderedView = sAlertRender();
        Meteor.setTimeout(function () {
            done();
        }, 1500);
    });
    it('should get called after 1000ms timeout', function (done) {
        chai.expect(isClosed).to.be.true;
        done();
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});

describe('sAlert onClose callback with 1800ms timeout param', function () {
    var renderedView;
    var sAlertId;
    var isClosed;
    before(function (done) {
        sAlertId = sAlert.success('Test onClose callback...', {timeout: 1800, onClose: function() {isClosed = true;}});
        renderedView = sAlertRender();
        Meteor.setTimeout(function () {
            done();
        }, 1000);
    });
    it('should not get called before 1800ms timeout', function (done) {
        chai.expect(isClosed).to.be.undefined;
        done();
    });
    after(function () {
        sAlert.closeAll();
        Blaze.remove(renderedView);
    });
});


================================================
FILE: versions.json
================================================
{
  "dependencies": [
    [
      "blaze",
      "1.0.0"
    ],
    [
      "deps",
      "1.0.0"
    ],
    [
      "ejson",
      "1.0.0"
    ],
    [
      "geojson-utils",
      "1.0.0"
    ],
    [
      "htmljs",
      "1.0.0-cordova1"
    ],
    [
      "id-map",
      "1.0.0"
    ],
    [
      "jquery",
      "1.0.0"
    ],
    [
      "json",
      "1.0.0"
    ],
    [
      "meteor",
      "1.0.2-cordova1"
    ],
    [
      "minimongo",
      "1.0.0"
    ],
    [
      "observe-sequence",
      "1.0.0"
    ],
    [
      "ordered-dict",
      "1.0.0"
    ],
    [
      "random",
      "1.0.0"
    ],
    [
      "templating",
      "1.0.4"
    ],
    [
      "ui",
      "1.0.0"
    ],
    [
      "underscore",
      "1.0.0"
    ]
  ],
  "pluginDependencies": [],
  "toolVersion": "meteor-tool@1.0.25",
  "format": "1.0"
}
Download .txt
gitextract_1e02wi4t/

├── .versions
├── CHANGELOG.md
├── README.md
├── client/
│   ├── s-alert-collection.js
│   ├── s-alert-default.css
│   ├── s-alert-template.html
│   ├── s-alert-template.js
│   └── s-alert.js
├── package.js
├── tests/
│   └── s-alert-test.js
└── versions.json
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
  {
    "path": ".versions",
    "chars": 482,
    "preview": "base64@1.0.3\nbinary-heap@1.0.3\nblaze@2.1.2\nblaze-tools@1.0.3\ncallback-hook@1.0.3\ncheck@1.0.5\nddp@1.1.0\ndeps@1.0.7\nejson@"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2505,
    "preview": "### Changelog\n#### v3.2.0\n- ready for Meteor 1.4 [#56](https://github.com/juliancwirko/meteor-s-alert/pull/56)\n\n#### v3."
  },
  {
    "path": "README.md",
    "chars": 12396,
    "preview": "### Simple and fancy notifications for Meteor\n\n- Website: [http://s-alert.meteorapp.com/](http://s-alert.meteorapp.com/)"
  },
  {
    "path": "client/s-alert-collection.js",
    "chars": 105,
    "preview": "'use strict';\n\n// only client side collections for now..\nsAlert.collection = new Mongo.Collection(null);\n"
  },
  {
    "path": "client/s-alert-default.css",
    "chars": 2620,
    "preview": "/* Common, default styles for the notification box */\n\n.s-alert-box,\n.s-alert-box * {\n\tbox-sizing: border-box;\n}\n\n.s-ale"
  },
  {
    "path": "client/s-alert-template.html",
    "chars": 881,
    "preview": "<template name=\"sAlert\">\n    {{#each sAlertDataLeft}}\n        {{> sAlertContent}}\n    {{/each}}\n    {{#each sAlertDataRi"
  },
  {
    "path": "client/s-alert-template.js",
    "chars": 6538,
    "preview": "'use strict';\n\nvar getAlertData = function (currentData, sAlertPosition) {\n    var positionTop = 0;\n    var positionBott"
  },
  {
    "path": "client/s-alert.js",
    "chars": 5394,
    "preview": "'use strict';\n\n// helper functions\nvar conditionSet = function (self, msg, condition, customSettings) {\n    var settings"
  },
  {
    "path": "package.js",
    "chars": 1035,
    "preview": "Package.describe({\n    'summary': 'Simple and fancy notifications / alerts / errors for Meteor',\n    'version': '3.2.0',"
  },
  {
    "path": "tests/s-alert-test.js",
    "chars": 16673,
    "preview": "'use strict';\n\nvar sAlertRender = function () {\n    var body = document.getElementsByTagName('body')[0];\n    return Blaz"
  },
  {
    "path": "versions.json",
    "chars": 842,
    "preview": "{\n  \"dependencies\": [\n    [\n      \"blaze\",\n      \"1.0.0\"\n    ],\n    [\n      \"deps\",\n      \"1.0.0\"\n    ],\n    [\n      \"ej"
  }
]

About this extraction

This page contains the full source code of the juliancwirko/meteor-s-alert GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (48.3 KB), approximately 12.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!