mirror of
https://github.com/vale981/Vulcan
synced 2025-03-04 17:21:37 -05:00
add embedly and newsletter packages
This commit is contained in:
parent
8ae358181e
commit
733f367f37
29 changed files with 1435 additions and 43 deletions
44
packages/.gitignore
vendored
44
packages/.gitignore
vendored
|
@ -1,44 +1,2 @@
|
|||
router
|
||||
momentjs
|
||||
database-forms
|
||||
paginated-subscription
|
||||
crypto-md5
|
||||
page-js-ie-support
|
||||
HTML5-History-API
|
||||
crypto-base
|
||||
iron-router
|
||||
nprogress
|
||||
i18next-meteor
|
||||
/momentjs
|
||||
/crypto-base
|
||||
/database-forms
|
||||
/crypto-md5
|
||||
/iron-router
|
||||
/nprogress
|
||||
/fast-render
|
||||
/spin
|
||||
/blaze-layout
|
||||
/autoform
|
||||
/simple-schema
|
||||
/collection2
|
||||
/moment
|
||||
/iron-router-progress
|
||||
/jquery-hotkeys
|
||||
/marked
|
||||
/bootstrap3-datepicker
|
||||
/subs-manager
|
||||
/telescope-module-embedly
|
||||
/telescope-recently-commented
|
||||
/iron-layout
|
||||
/iron-core
|
||||
/iron-dynamic-template
|
||||
/handlebars-server
|
||||
/npm
|
||||
/mailchimp
|
||||
/synced-cron
|
||||
/cookies
|
||||
/telescope-newsletter
|
||||
/accounts-entry
|
||||
/accounts-t9n
|
||||
/simple-form
|
||||
/underscore-string-latest
|
||||
/npm-container
|
7
packages/telescope-module-embedly/History.md
Normal file
7
packages/telescope-module-embedly/History.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
### v0.2.9
|
||||
|
||||
- Update to Meteor 0.9.0.
|
||||
|
||||
### v0.2.8
|
||||
|
||||
- Don't display image if it cannot be found.
|
12
packages/telescope-module-embedly/README.md
Normal file
12
packages/telescope-module-embedly/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
The Telescope [Embedly](http://embed.ly) module.
|
||||
|
||||
More details in [this blog post](http://telesc.pe/blog/extending-telescope-with-embedly-thumbnails/).
|
||||
|
||||
### Install
|
||||
|
||||
1. `mrt add telescope-module-embedly`.
|
||||
2. Go to the Telescope settings page and add your Embedly API key.
|
||||
|
||||
### Extra Features
|
||||
|
||||
When linking to videos, the thumbnail becomes clickable and opens the video in a lightbox.
|
|
@ -0,0 +1,79 @@
|
|||
.post-thumbnail{
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
|
||||
.post-thumbnail-link{
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
max-width: 80px;
|
||||
max-height: 50px;
|
||||
}
|
||||
|
||||
.post-thumbnail-image{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-thumbnail-has-video:after{
|
||||
content: '‣';
|
||||
font-size: 40px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
margin: -18px 0 0 -18px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
display: block;
|
||||
z-index: 10;
|
||||
text-shadow: 0px 1px 2px rgba(0,0,0,0.5);
|
||||
background: rgba(0,0,0,0.4);
|
||||
border: 2px solid rgba(255,255,255,0.8);
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 30em) {
|
||||
|
||||
.post-thumbnail{
|
||||
float: right;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.post-video-lightbox{
|
||||
position: fixed;
|
||||
background: rgba(0,0,0,0.7);
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10000;
|
||||
}
|
||||
.post-video-lightbox-inner{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform:translate(-50%, -50%);
|
||||
-moz-transform:translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20000;
|
||||
}
|
||||
.post-video-lightbox-hide, .post-video-lightbox-hide:link, .post-video-lightbox-hide:visited, .post-video-lightbox-hide:hover{
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
color: white;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<template name="postThumbnail">
|
||||
{{#if thumbnailUrl}}
|
||||
<a class="post-thumbnail-link {{playVideoClass}}" href="{{postLink}}" target="_blank">
|
||||
<img class="post-thumbnail-image" src="{{thumbnailUrl}}" onerror="this.style.display='none';"/>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if media}}
|
||||
{{> UI.dynamic template=videoTemplate data=this}}
|
||||
{{/if}}
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
Template[getTemplate('postThumbnail')].helpers({
|
||||
postLink: function () {
|
||||
return !!this.url ? getOutgoingUrl(this.url) : "/posts/"+this._id;
|
||||
},
|
||||
playVideoClass: function () {
|
||||
return !!this.media ? 'post-thumbnail-has-video': '';
|
||||
},
|
||||
videoTemplate: function () {
|
||||
return getTemplate('postVideo');
|
||||
}
|
||||
});
|
||||
|
||||
Template[getTemplate('postThumbnail')].events({
|
||||
'click .post-thumbnail-has-video': function (e) {
|
||||
e.preventDefault();
|
||||
$('body').addClass('showing-lightbox');
|
||||
$(e.target).parents('.post').find('.post-video-lightbox').fadeIn('fast');
|
||||
}
|
||||
})
|
10
packages/telescope-module-embedly/lib/client/post_video.html
Normal file
10
packages/telescope-module-embedly/lib/client/post_video.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<template name="postVideo">
|
||||
{{#with media}}
|
||||
<div class="post-video-lightbox hidden">
|
||||
<a class="post-video-lightbox-hide" href="#">×</a>
|
||||
<div class="post-video-lightbox-inner">
|
||||
{{{html}}}
|
||||
</div>
|
||||
</div>
|
||||
{{/with}}
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
Template[getTemplate('postVideo')].events({
|
||||
'click .post-video-lightbox-hide, click .post-video-lightbox': function (e) {
|
||||
e.preventDefault();
|
||||
$(e.target).parents('.post').find('.post-video-lightbox').fadeOut('fast');
|
||||
$('body').removeClass('showing-lightbox');
|
||||
}
|
||||
})
|
33
packages/telescope-module-embedly/lib/embedly.js
Normal file
33
packages/telescope-module-embedly/lib/embedly.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
var thumbnailProperty = {
|
||||
propertyName: 'thumbnailUrl',
|
||||
propertySchema: {
|
||||
type: String,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
addToPostSchema.push(thumbnailProperty);
|
||||
|
||||
var mediaProperty = {
|
||||
propertyName: 'media',
|
||||
propertySchema: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true
|
||||
}
|
||||
}
|
||||
addToPostSchema.push(mediaProperty);
|
||||
|
||||
|
||||
postModules.push({
|
||||
template: 'postThumbnail',
|
||||
position: 'center-left'
|
||||
});
|
||||
|
||||
var embedlyKeyProperty = {
|
||||
propertyName: 'embedlyKey',
|
||||
propertySchema: {
|
||||
type: String,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(embedlyKeyProperty);
|
|
@ -0,0 +1,65 @@
|
|||
getEmbedlyData = function (url) {
|
||||
var data = {}
|
||||
var extractBase = 'http://api.embed.ly/1/extract';
|
||||
var embedlyKey = getSetting('embedlyKey');
|
||||
|
||||
try {
|
||||
|
||||
if(!embedlyKey)
|
||||
throw new Error("Couldn't find an Embedly API key! Please add it to your Telescope settings.")
|
||||
|
||||
var result = Meteor.http.get(extractBase, {
|
||||
params: {
|
||||
key: embedlyKey,
|
||||
url: url,
|
||||
image_width: 200,
|
||||
image_height: 150,
|
||||
image_method: 'crop'
|
||||
}
|
||||
});
|
||||
|
||||
if(!result.data.images.length)
|
||||
throw new Error("Couldn't find an image!");
|
||||
|
||||
data.thumbnailUrl = result.data.images[0].url;
|
||||
|
||||
if(typeof result.data.media !== 'undefined')
|
||||
data.media = result.data.media
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
testGetEmbedlyData: function (url) {
|
||||
console.log(getEmbedlyData(url))
|
||||
},
|
||||
setThumbnail: function (post) {
|
||||
var set = {};
|
||||
if(post.url){
|
||||
var data = getEmbedlyData(post.url);
|
||||
if(!!data && !!data.thumbnailUrl)
|
||||
set.thumbnailUrl = data.thumbnailUrl;
|
||||
if(!!data && !!data.media.html)
|
||||
set.media = data.media
|
||||
console.log(set)
|
||||
Posts.update({_id: post._id}, {$set: set});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var extendPost = function (post) {
|
||||
if(post.url){
|
||||
var data = getEmbedlyData(post.url);
|
||||
if(!!data && !!data.thumbnailUrl)
|
||||
post.thumbnailUrl = data.thumbnailUrl;
|
||||
if(!!data && !!data.media.html)
|
||||
post.media = data.media
|
||||
}
|
||||
return post;
|
||||
}
|
||||
|
||||
postSubmitServerCallbacks.push(extendPost);
|
29
packages/telescope-module-embedly/package.js
Normal file
29
packages/telescope-module-embedly/package.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
Package.describe({
|
||||
summary: "Telescope Embedly module package",
|
||||
version: '0.2.9',
|
||||
name: "telescope-module-embedly",
|
||||
git: 'https://github.com/TelescopeJS/Telescope-Module-Embedly.git'
|
||||
});
|
||||
|
||||
Package.onUse( function(api) {
|
||||
|
||||
api.versionsFrom("METEOR@0.9.0");
|
||||
|
||||
api.use(['telescope-lib', 'telescope-base'], ['client', 'server']);
|
||||
|
||||
api.use(['http'], ['server']);
|
||||
|
||||
api.use(['templating'], ['client']);
|
||||
|
||||
api.add_files(['lib/embedly.js'], ['client', 'server']);
|
||||
|
||||
api.add_files(['lib/server/get_embedly_data.js'], ['server']);
|
||||
|
||||
api.add_files([
|
||||
'lib/client/post_thumbnail.html',
|
||||
'lib/client/post_thumbnail.js',
|
||||
'lib/client/post_thumbnail.css',
|
||||
'lib/client/post_video.html',
|
||||
'lib/client/post_video.js'
|
||||
], ['client']);
|
||||
});
|
8
packages/telescope-module-embedly/smart.json
Normal file
8
packages/telescope-module-embedly/smart.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "telescope-module-embedly",
|
||||
"description": "The Telescope Embedly module.",
|
||||
"homepage": "https://github.com/TelescopeJS/telescope-module-embedly",
|
||||
"author": "Sacha Greif <info@sachagreif.com> (http://sachagreif.com)",
|
||||
"version": "0.2.8",
|
||||
"git": "https://github.com/TelescopeJS/telescope-module-embedly.git"
|
||||
}
|
87
packages/telescope-module-embedly/versions.json
Normal file
87
packages/telescope-module-embedly/versions.json
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"dependencies": [
|
||||
[
|
||||
"blaze",
|
||||
"1.0.3"
|
||||
],
|
||||
[
|
||||
"deps",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"ejson",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"geojson-utils",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"htmljs",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"http",
|
||||
"1.0.3"
|
||||
],
|
||||
[
|
||||
"id-map",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"jquery",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"json",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"meteor",
|
||||
"1.0.2"
|
||||
],
|
||||
[
|
||||
"minimongo",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"observe-sequence",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"ordered-dict",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"random",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"telescope-base",
|
||||
"0.0.0"
|
||||
],
|
||||
[
|
||||
"telescope-i18n",
|
||||
"0.0.0"
|
||||
],
|
||||
[
|
||||
"telescope-lib",
|
||||
"0.2.9"
|
||||
],
|
||||
[
|
||||
"templating",
|
||||
"1.0.4"
|
||||
],
|
||||
[
|
||||
"ui",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"underscore",
|
||||
"1.0.0"
|
||||
]
|
||||
],
|
||||
"pluginDependencies": [],
|
||||
"toolVersion": "meteor-tool@1.0.26",
|
||||
"format": "1.0"
|
||||
}
|
8
packages/telescope-newsletter/History.md
Normal file
8
packages/telescope-newsletter/History.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## v0.1.6
|
||||
|
||||
- Return error message or campaign subject for cron job.
|
||||
- Change Newsletter Frequency option to a `select` input.
|
||||
|
||||
## v0.1.5
|
||||
|
||||
- Added option to show/hide newsletter sign-up banner
|
50
packages/telescope-newsletter/README.md
Normal file
50
packages/telescope-newsletter/README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Telescope Newsletter
|
||||
|
||||
This package schedules an automatic newsletter digest.
|
||||
|
||||

|
||||
|
||||
### Install
|
||||
|
||||
1. `mrt add telescope-newsletter`.
|
||||
2. Go to the Telescope settings page and add your MailChimp API key and List ID.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [meteor-mailchimp](https://github.com/MiroHibler/meteor-mailchimp/)
|
||||
- [synced-cron](https://github.com/percolatestudio/meteor-synced-cron)
|
||||
- [handlebars-server](https://github.com/EventedMind/meteor-handlebars-server)
|
||||
- [meteor-npm](https://github.com/arunoda/meteor-npm/)
|
||||
|
||||
### Settings
|
||||
|
||||
- **Show Banner**:
|
||||
- **MailChimp API Key**:
|
||||
- **MailChimp List ID**:
|
||||
- **Newsletter Frequency**: Choose from every day, three times a week, and once a week. Note that changes to this setting require you to restart your app to take effect.
|
||||
- **Posts Per Newsletter**: how many posts each newsletter should contain.
|
||||
|
||||
Note that for this package to work properly, you'll also need to fill in the **Default Email** setting.
|
||||
|
||||
### How It Works
|
||||
|
||||
The package works with [MailChimp](http://mailchimp.com), which means you'll need to fill in an API key and List ID in your Telescope app's settings panel.
|
||||
|
||||
Every `x` days, it builds a digest consisting of the top `y` items posted in the past `x` days that haven't yet been sent out in a newsletter.
|
||||
|
||||
It then creates a campaign in MailChimp and schedules it to be sent out **one hour later**, and sends you a confirmation email (to give you some time to check that everything looks good).
|
||||
|
||||
### Test Routes
|
||||
|
||||
If you want to preview your email templates, you can do so at the following routes:
|
||||
|
||||
- **Digest**: [http://localhost:3000/email/campaign](http://localhost:3000/email/campaign)
|
||||
- **Confirmation**: [http://localhost:3000/email/digest-confirmation](http://localhost:3000/email/digest-confirmation)
|
||||
|
||||
(Replace `http://localhost:3000` with your app's URL)
|
||||
|
||||
### Newsletter Sign-Up Banner
|
||||
|
||||
This package also includes a newsletter sign-up banner that uses the MailChimp API to add people to your list.
|
||||
|
||||

|
102
packages/telescope-newsletter/lib/client/newsletter_banner.css
Normal file
102
packages/telescope-newsletter/lib/client/newsletter_banner.css
Normal file
|
@ -0,0 +1,102 @@
|
|||
.newsletter-banner.newsletter-banner{
|
||||
background: rgba(255,255,255,0.7);
|
||||
margin-bottom: 10px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.newsletter-banner form{
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.newsletter-tagline{
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.newsletter-subscribed{
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
opacity: 0;
|
||||
line-height: 74px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.newsletter-banner form, .newsletter-subscribed{
|
||||
-webkit-transition:opacity 0.3s ease-out;
|
||||
-moz-transition:opacity 0.3s ease-out;
|
||||
-o-transition:opacity 0.3s ease-out;
|
||||
transition:opacity 0.3s ease-out;
|
||||
}
|
||||
.newsletter-email.newsletter-email{
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
border-color: rgba(0,0,0,0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.button.newsletter-button{
|
||||
display: inline-block;
|
||||
float: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.show-loader{
|
||||
pointer-events:none;
|
||||
}
|
||||
.show-loader .newsletter-tagline, .show-loader .newsletter-email{
|
||||
opacity: 0.35;
|
||||
}
|
||||
.show-loader .newsletter-button{
|
||||
color: rgba(255,255,255,0); /* hide button text */
|
||||
}
|
||||
.button-loader{
|
||||
display: none;
|
||||
}
|
||||
.show-loader .button-loader{
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
display: block;
|
||||
}
|
||||
.newsletter-dismiss, .newsletter-dismiss:link, .newsletter-dismiss:visited{
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 100%;
|
||||
background: rgba(0,0,0,0.15);
|
||||
color: white;
|
||||
top: 50%;
|
||||
margin-top: -15px;
|
||||
right: 20px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 26px;
|
||||
}
|
||||
.newsletter-dismiss:hover{
|
||||
color: inherit;
|
||||
}
|
||||
@media screen and (max-width: 30em) {
|
||||
.newsletter-dismiss, .newsletter-dismiss:link{
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
.newsletter-tagline{
|
||||
padding: 0 30px;
|
||||
}
|
||||
.newsletter-tagline, .newsletter-email, .newsletter-button{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<template name="newsletterBanner">
|
||||
{{#if showBanner}}
|
||||
<div class="newsletter-banner grid-module grid">
|
||||
<form>
|
||||
<h4 class="newsletter-tagline">Receive the best of {{siteName}} right in your inbox</h4>
|
||||
{{#if isNotConnected}}
|
||||
<input class="newsletter-email" type="email" placeholder="Your Email">
|
||||
{{/if}}
|
||||
<button class="button newsletter-button">Get Newsletter<span class="button-loader"><img src="/img/loading-balls.svg"/></span></button>
|
||||
</form>
|
||||
<h4 class="newsletter-subscribed">Thanks for subscribing!</h4>
|
||||
<a href="#" class="newsletter-dismiss">×</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
|
@ -0,0 +1,87 @@
|
|||
var confirmSubscription = function () {
|
||||
$('.newsletter-banner form').css('opacity', 0);
|
||||
$('.newsletter-banner .newsletter-subscribed').css('display', 'block').css('opacity', 1);
|
||||
Meteor.setInterval(function () {
|
||||
// required because otherwise banner disappears immediately after confirmation
|
||||
dismissBanner();
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
var dismissBanner = function () {
|
||||
$('.newsletter-banner').fadeOut('fast', function () {
|
||||
if(Meteor.user()){
|
||||
// if user is connected, change setting in their account
|
||||
setUserSetting('showBanner', false);
|
||||
}else{
|
||||
// set cookie
|
||||
Cookie.set('showBanner', "no");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Template[getTemplate('newsletterBanner')].helpers({
|
||||
siteName: function () {
|
||||
return getSetting('title');
|
||||
},
|
||||
isNotConnected: function () {
|
||||
return !Meteor.user()
|
||||
},
|
||||
showBanner: function () {
|
||||
// note: should not be reactive
|
||||
if(
|
||||
getSetting('showBanner', false) == false
|
||||
|| Router.current().path != '/'
|
||||
|| Cookie.get('showBanner') == "no"
|
||||
|| (Meteor.user() && getUserSetting('showBanner', true) == false)
|
||||
|| (Meteor.user() && getUserSetting('subscribedToNewsletter', false) == true)
|
||||
){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template[getTemplate('newsletterBanner')].events({
|
||||
'click .newsletter-button': function (e) {
|
||||
e.preventDefault();
|
||||
var $banner = $('.newsletter-banner');
|
||||
if(Meteor.user()){
|
||||
$banner.addClass('show-loader');
|
||||
Meteor.call('addCurrentUserToMailChimpList', function (error, result) {
|
||||
$banner.removeClass('show-loader');
|
||||
if(error){
|
||||
console.log(error);
|
||||
throwError(error.message);
|
||||
}else{
|
||||
console.log(result);
|
||||
confirmSubscription();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
var email = $('.newsletter-email').val();
|
||||
if(!email){
|
||||
alert('Please fill in your email.');
|
||||
return
|
||||
}
|
||||
$banner.addClass('show-loader');
|
||||
Meteor.call('addEmailToMailChimpList', email, function (error, result) {
|
||||
$banner.removeClass('show-loader');
|
||||
if(error){
|
||||
console.log(error);
|
||||
throwError(error.message);
|
||||
}else{
|
||||
console.log(result);
|
||||
confirmSubscription();
|
||||
}
|
||||
});
|
||||
}
|
||||
// $('body').addClass('showing-lightbox');
|
||||
// $(e.target).parents('.post').find('.post-video-lightbox').fadeIn('fast');
|
||||
},
|
||||
'click .newsletter-dismiss': function (e) {
|
||||
$('.newsletter-banner').fadeOut('fast');
|
||||
dismissBanner();
|
||||
e.preventDefault();
|
||||
}
|
||||
})
|
132
packages/telescope-newsletter/lib/newsletter.js
Normal file
132
packages/telescope-newsletter/lib/newsletter.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
campaignSchema = new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
optional: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
optional: true
|
||||
},
|
||||
sentAt: {
|
||||
type: String,
|
||||
optional: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
optional: true
|
||||
},
|
||||
posts: {
|
||||
type: [String],
|
||||
optional: true
|
||||
},
|
||||
webHits: {
|
||||
type: Number,
|
||||
optional: true
|
||||
},
|
||||
});
|
||||
|
||||
Campaigns = new Meteor.Collection("campaigns", {
|
||||
schema: campaignSchema
|
||||
});
|
||||
|
||||
addToPostSchema.push(
|
||||
{
|
||||
propertyName: 'scheduledAt',
|
||||
propertySchema: {
|
||||
type: Date,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Settings
|
||||
|
||||
// note for next two fields: need to add a way to tell app not to publish field to client except for admins
|
||||
|
||||
var showBanner = {
|
||||
propertyName: 'showBanner',
|
||||
propertySchema: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
label: 'Show newsletter sign-up banner'
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(showBanner);
|
||||
|
||||
var mailChimpAPIKey = {
|
||||
propertyName: 'mailChimpAPIKey',
|
||||
propertySchema: {
|
||||
type: String,
|
||||
optional: true,
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(mailChimpAPIKey);
|
||||
|
||||
var mailChimpListId = {
|
||||
propertyName: 'mailChimpListId',
|
||||
propertySchema: {
|
||||
type: String,
|
||||
optional: true,
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(mailChimpListId);
|
||||
|
||||
var postsPerNewsletter = {
|
||||
propertyName: 'postsPerNewsletter',
|
||||
propertySchema: {
|
||||
type: Number,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(postsPerNewsletter);
|
||||
|
||||
var newsletterFrequency = {
|
||||
propertyName: 'newsletterFrequency',
|
||||
propertySchema: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
autoform: {
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
label: 'Every Day'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Mondays, Wednesdays, Fridays'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'Mondays & Thursdays'
|
||||
},
|
||||
{
|
||||
value: 7,
|
||||
label: 'Once a week (Mondays)'
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
label: "Don't send newsletter"
|
||||
}
|
||||
]
|
||||
},
|
||||
label: 'Newsletter Frequency (requires restart)'
|
||||
}
|
||||
}
|
||||
addToSettingsSchema.push(newsletterFrequency);
|
||||
|
||||
// create new "campaign" lens for all posts from the past X days that haven't been scheduled yet
|
||||
viewParameters.campaign = function (terms) {
|
||||
return {
|
||||
find: {
|
||||
scheduledAt: {$exists: false},
|
||||
postedAt: {
|
||||
$gte: terms.after
|
||||
}
|
||||
},
|
||||
options: {sort: {sticky: -1, score: -1}}
|
||||
};
|
||||
}
|
||||
|
||||
heroModules.push({
|
||||
template: 'newsletterBanner'
|
||||
});
|
85
packages/telescope-newsletter/lib/server/campaign.js
Normal file
85
packages/telescope-newsletter/lib/server/campaign.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
defaultFrequency = 7;
|
||||
defaultPosts = 5;
|
||||
|
||||
getCampaignPosts = function (postsCount) {
|
||||
|
||||
var newsletterFrequency = getSetting('newsletterFrequency', defaultFrequency);
|
||||
|
||||
// look for last scheduled campaign in the database
|
||||
var lastCampaign = SyncedCron._collection.findOne({name: 'Schedule newsletter'}, {sort: {finishedAt: -1}, limit: 1});
|
||||
|
||||
// if there is a last campaign use its date, else default to posts from the last 7 days
|
||||
var lastWeek = moment().subtract('days', 7).toDate();
|
||||
var after = (typeof lastCampaign != 'undefined') ? lastCampaign.finishedAt : lastWeek
|
||||
|
||||
var params = getParameters({
|
||||
view: 'campaign',
|
||||
limit: postsCount,
|
||||
after: after
|
||||
});
|
||||
return Posts.find(params.find, params.options).fetch();
|
||||
}
|
||||
|
||||
buildCampaign = function (postsArray) {
|
||||
var postsHTML = '', subject = '';
|
||||
|
||||
// 1. Iterate through posts and pass each of them through a handlebars template
|
||||
postsArray.forEach(function (post, index) {
|
||||
if(index > 0)
|
||||
subject += ', ';
|
||||
|
||||
subject += post.title;
|
||||
|
||||
var postUser = Meteor.users.findOne(post.userId);
|
||||
|
||||
// the naked post object as stored in the database is missing a few properties, so let's add them
|
||||
var properties = _.extend(post, {
|
||||
authorName: getAuthorName(post),
|
||||
postLink: getPostLink(post),
|
||||
profileUrl: getProfileUrl(postUser),
|
||||
postPageLink: getPostPageUrl(post),
|
||||
date: moment(post.postedAt).format("MMMM D YYYY")
|
||||
});
|
||||
|
||||
if (post.body)
|
||||
properties.body = marked(trimWords(post.body, 20)).replace('<p>', '').replace('</p>', ''); // remove p tags
|
||||
|
||||
if(post.url)
|
||||
properties.domain = getDomain(post.url)
|
||||
|
||||
postsHTML += getEmailTemplate('emailPostItem')(properties);
|
||||
});
|
||||
|
||||
// 2. Wrap posts HTML in digest template
|
||||
var digestHTML = getEmailTemplate('emailDigest')({
|
||||
siteName: getSetting('title'),
|
||||
date: moment().format("dddd, MMMM Do YYYY"),
|
||||
content: postsHTML
|
||||
});
|
||||
|
||||
// 3. wrap digest HTML in email wrapper tempalte
|
||||
var emailHTML = buildEmailTemplate(digestHTML);
|
||||
|
||||
return {
|
||||
postIds: _.pluck(postsArray, '_id'),
|
||||
subject: trimWords(subject, 15),
|
||||
html: emailHTML
|
||||
}
|
||||
}
|
||||
|
||||
scheduleNextCampaign = function () {
|
||||
var posts = getCampaignPosts(getSetting('postsPerNewsletter', defaultPosts));
|
||||
if(!!posts.length){
|
||||
return scheduleCampaign(buildCampaign(posts))
|
||||
}else{
|
||||
var result = 'No posts to schedule today…';
|
||||
console.log(result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
testCampaign: function () {
|
||||
scheduleNextCampaign();
|
||||
}
|
||||
});
|
74
packages/telescope-newsletter/lib/server/cron.js
Normal file
74
packages/telescope-newsletter/lib/server/cron.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
Later = Npm.require('later');
|
||||
|
||||
defaultFrequency = 7; // once a week
|
||||
|
||||
getSchedule = function (parser) {
|
||||
var frequency = getSetting('newsletterFrequency', defaultFrequency);
|
||||
switch (frequency) {
|
||||
case 1: // every day
|
||||
// sched = {schedules: [{dw: [1,2,3,4,5,6,0]}]};
|
||||
return parser.recur().on(1,2,3,4,5,6,0).dayOfWeek();
|
||||
|
||||
case 2: // Mondays, Wednesdays, Fridays
|
||||
// sched = {schedules: [{dw: [2,4,6]}]};
|
||||
return parser.recur().on(2,4,6).dayOfWeek();
|
||||
|
||||
case 3: // Mondays, Thursdays
|
||||
// sched = {schedules: [{dw: [2,5]}]};
|
||||
return parser.recur().on(2,5).dayOfWeek();
|
||||
|
||||
case 7: // Once a week (Mondays)
|
||||
// sched = {schedules: [{dw: [2]}]};
|
||||
return parser.recur().on(2).dayOfWeek();
|
||||
|
||||
default: // Don't send
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
SyncedCron.getNext = function (jobName) {
|
||||
var scheduledAt;
|
||||
try {
|
||||
this._entries.some(function(entry) {
|
||||
if(entry.name === jobName){
|
||||
var schedule = entry.schedule(Later.parse);
|
||||
scheduledAt = Later.schedule(schedule).next(1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
scheduledAt = 'No job scheduled';
|
||||
}
|
||||
return scheduledAt;
|
||||
}
|
||||
|
||||
getNextCampaignSchedule = function () {
|
||||
return SyncedCron.getNext('Schedule newsletter');
|
||||
}
|
||||
|
||||
SyncedCron.add({
|
||||
name: 'Schedule newsletter',
|
||||
schedule: function(parser) {
|
||||
// parser is a later.parse object
|
||||
// var sched;
|
||||
return getSchedule(parser)
|
||||
|
||||
},
|
||||
job: function() {
|
||||
scheduleNextCampaign();
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.startup(function() {
|
||||
if(getSetting('newsletterFrequency', defaultFrequency) != 0) {
|
||||
SyncedCron.start();
|
||||
};
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
getNextJob: function (jobName) {
|
||||
return SyncedCron.getNext(jobName);
|
||||
}
|
||||
});
|
141
packages/telescope-newsletter/lib/server/mailchimp.js
Normal file
141
packages/telescope-newsletter/lib/server/mailchimp.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
scheduleCampaign = function (campaign) {
|
||||
MailChimpOptions.apiKey = getSetting('mailChimpAPIKey');
|
||||
MailChimpOptions.listId = getSetting('mailChimpListId');
|
||||
|
||||
var htmlToText = Meteor.require('html-to-text');
|
||||
var text = htmlToText.fromString(campaign.html, {
|
||||
wordwrap: 130
|
||||
});
|
||||
var defaultEmail = getSetting('defaultEmail');
|
||||
var result= '';
|
||||
|
||||
if(!!MailChimpOptions.apiKey && !!MailChimpOptions.listId){
|
||||
|
||||
console.log( 'Creating campaign…');
|
||||
|
||||
try {
|
||||
var api = new MailChimp();
|
||||
} catch ( error ) {
|
||||
console.log( error.message );
|
||||
}
|
||||
|
||||
api.call( 'campaigns', 'create', {
|
||||
type: 'regular',
|
||||
options: {
|
||||
list_id: MailChimpOptions.listId,
|
||||
subject: campaign.subject,
|
||||
from_email: getSetting('defaultEmail'),
|
||||
from_name: getSetting('title')+ ' Top Posts',
|
||||
},
|
||||
content: {
|
||||
html: campaign.html,
|
||||
text: text
|
||||
}
|
||||
}, Meteor.bindEnvironment(function ( error, result ) {
|
||||
if ( error ) {
|
||||
console.log( error.message );
|
||||
result = error.message;
|
||||
} else {
|
||||
console.log( 'Campaign created');
|
||||
// console.log( JSON.stringify( result ) );
|
||||
|
||||
var cid = result.id;
|
||||
var archive_url = result.archive_url;
|
||||
var scheduledTime = moment().zone(0).add('hours', 1).format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
api.call('campaigns', 'schedule', {
|
||||
cid: cid,
|
||||
schedule_time: scheduledTime
|
||||
}, Meteor.bindEnvironment(function ( error, result) {
|
||||
if (error) {
|
||||
console.log( error.message );
|
||||
result = error.message;
|
||||
}else{
|
||||
console.log('Campaign scheduled for '+scheduledTime);
|
||||
console.log(campaign.subject)
|
||||
// console.log( JSON.stringify( result ) );
|
||||
|
||||
// mark posts as sent
|
||||
Posts.update({_id: {$in: campaign.postIds}}, {$set: {scheduledAt: new Date()}}, {multi: true})
|
||||
|
||||
// send confirmation email
|
||||
var confirmationHtml = getEmailTemplate('emailDigestConfirmation')({
|
||||
time: scheduledTime,
|
||||
newsletterLink: archive_url,
|
||||
subject: campaign.subject
|
||||
});
|
||||
sendEmail(defaultEmail, 'Newsletter scheduled', buildEmailTemplate(confirmationHtml));
|
||||
result = campaign.subject;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
addToMailChimpList = function(userOrEmail, confirm, done){
|
||||
var user, email;
|
||||
|
||||
if(typeof userOrEmail == "string"){
|
||||
user = null;
|
||||
email = userOrEmail;
|
||||
}else if(typeof userOrEmail == "object"){
|
||||
user = userOrEmail;
|
||||
email = getEmail(user);
|
||||
if (!email)
|
||||
throw 'User must have an email address';
|
||||
}
|
||||
|
||||
MailChimpOptions.apiKey = getSetting('mailChimpAPIKey');
|
||||
MailChimpOptions.listId = getSetting('mailChimpListId');
|
||||
// add a user to a MailChimp list.
|
||||
// called when a new user is created, or when an existing user fills in their email
|
||||
if(!!MailChimpOptions.apiKey && !!MailChimpOptions.listId){
|
||||
|
||||
console.log('adding "'+email+'" to MailChimp list…');
|
||||
|
||||
try {
|
||||
var api = new MailChimp();
|
||||
} catch ( error ) {
|
||||
console.log( error.message );
|
||||
}
|
||||
|
||||
api.call( 'lists', 'subscribe', {
|
||||
id: MailChimpOptions.listId,
|
||||
email: {"email": email},
|
||||
double_optin: confirm
|
||||
}, Meteor.bindEnvironment(function ( error, result ) {
|
||||
if ( error ) {
|
||||
console.log( error.message );
|
||||
done(error, null);
|
||||
} else {
|
||||
console.log( JSON.stringify( result ) );
|
||||
if(!!user)
|
||||
setUserSetting('subscribedToNewsletter', true, user);
|
||||
done(null, result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
syncAddToMailChimpList = Async.wrap(addToMailChimpList);
|
||||
|
||||
Meteor.methods({
|
||||
addCurrentUserToMailChimpList: function(){
|
||||
var currentUser = Meteor.users.findOne(this.userId);
|
||||
try {
|
||||
return syncAddToMailChimpList(currentUser, false);
|
||||
} catch (error) {
|
||||
throw new Meteor.Error(500, error.message);
|
||||
}
|
||||
},
|
||||
addEmailToMailChimpList: function (email) {
|
||||
try {
|
||||
return syncAddToMailChimpList(email, true);
|
||||
} catch (error) {
|
||||
throw new Meteor.Error(500, error.message);
|
||||
}
|
||||
}
|
||||
})
|
35
packages/telescope-newsletter/lib/server/routes.js
Normal file
35
packages/telescope-newsletter/lib/server/routes.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
Meteor.startup(function () {
|
||||
|
||||
Router.map(function() {
|
||||
|
||||
this.route('campaign', {
|
||||
where: 'server',
|
||||
path: '/email/campaign',
|
||||
action: function() {
|
||||
var campaign = buildCampaign(getCampaignPosts(getSetting('postsPerNewsletter', 5)));
|
||||
var campaignSubject = '<div class="campaign-subject"><strong>Subject:</strong> '+campaign.subject+' (note: contents might change)</div>';
|
||||
var campaignSchedule = '<div class="campaign-schedule"><strong>Scheduled for:</strong> '+getNextCampaignSchedule()+'</div>';
|
||||
|
||||
this.response.write(campaignSubject+campaignSchedule+campaign.html);
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
this.route('digestConfirmation', {
|
||||
where: 'server',
|
||||
path: '/email/digest-confirmation',
|
||||
action: function() {
|
||||
var confirmationHtml = getEmailTemplate('emailDigestConfirmation')({
|
||||
time: 'January 1st, 1901',
|
||||
newsletterLink: 'http://example.com',
|
||||
subject: 'Lorem ipsum dolor sit amet'
|
||||
});
|
||||
this.response.write(buildEmailTemplate(confirmationHtml));
|
||||
this.response.end();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
<style type="text/css">
|
||||
.email-digest{
|
||||
}
|
||||
.digest-date{
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
}
|
||||
.post-item{
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.post-date{
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
.post-title{
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.post-thumbnail{
|
||||
}
|
||||
.post-meta{
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.post-meta a{
|
||||
color: #333;
|
||||
}
|
||||
.post-domain{
|
||||
font-weight: bold;
|
||||
}
|
||||
.post-body-excerpt{
|
||||
font-size: 14px;
|
||||
}
|
||||
.post-body-excerpt p{
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="heading">Recently on {{siteName}}</span>
|
||||
<span class="digest-date">– {{date}}</span>
|
||||
<br><br>
|
||||
|
||||
<div class="email-digest">
|
||||
{{{content}}}
|
||||
</div>
|
||||
<br>
|
|
@ -0,0 +1,3 @@
|
|||
<span class="heading">Newsletter scheduled for {{time}}</span><br><br>
|
||||
|
||||
<a href="{{newsletterLink}}">{{subject}}</a><br><br>
|
|
@ -0,0 +1,34 @@
|
|||
<div class="post-item">
|
||||
<br >
|
||||
|
||||
<span class="post-title">
|
||||
{{#if thumbnailUrl}}
|
||||
<img class="post-thumbnail" src="{{thumbnailUrl}}"/>
|
||||
{{/if}}
|
||||
|
||||
<a href="{{postLink}}" target="_blank">{{title}}</a>
|
||||
</span>
|
||||
|
||||
<div class="post-meta">
|
||||
{{#if domain}}
|
||||
<a class="post-domain" href="">{{domain}}</a>
|
||||
|
|
||||
{{/if}}
|
||||
<span class="post-submitted">Submitted by <a href="{{profileUrl}}" class="comment-link" target="_blank">{{authorName}}</a></span>
|
||||
<span class="post-date">on {{date}}</span>
|
||||
|
|
||||
<a href="{{postPageLink}}" class="comment-link" target="_blank">{{comments}} Comments</a>
|
||||
</div>
|
||||
|
||||
|
||||
{{#if body}}
|
||||
<div class="post-body-excerpt">
|
||||
{{{body}}}
|
||||
<a href="{{postPageLink}}" class="comment-link" target="_blank">Read more</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
|
||||
<br>
|
||||
</div>
|
||||
|
51
packages/telescope-newsletter/package.js
Normal file
51
packages/telescope-newsletter/package.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
Package.describe({summary: "Telescope email digest package"});
|
||||
|
||||
Npm.depends({later: "1.1.6"});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
api.use([
|
||||
'telescope-lib',
|
||||
'telescope-base',
|
||||
'aldeed:simple-schema',
|
||||
'mrt:mailchimp'
|
||||
], ['client', 'server']);
|
||||
|
||||
api.use([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'iron:router',
|
||||
'templating',
|
||||
'mrt:cookies'
|
||||
], 'client');
|
||||
|
||||
api.use([
|
||||
'percolatestudio:synced-cron',
|
||||
'cmather:handlebars-server',
|
||||
'meteorhacks:npm'
|
||||
], ['server']);
|
||||
|
||||
api.add_files([
|
||||
'lib/newsletter.js'
|
||||
], ['client', 'server']);
|
||||
|
||||
api.add_files([
|
||||
'lib/client/newsletter_banner.html',
|
||||
'lib/client/newsletter_banner.js',
|
||||
'lib/client/newsletter_banner.css'
|
||||
], ['client']);
|
||||
|
||||
api.add_files([
|
||||
'lib/server/campaign.js',
|
||||
'lib/server/cron.js',
|
||||
'lib/server/mailchimp.js',
|
||||
'lib/server/routes.js',
|
||||
'lib/server/templates/emailDigest.handlebars',
|
||||
'lib/server/templates/emailDigestConfirmation.handlebars',
|
||||
'lib/server/templates/emailPostItem.handlebars'
|
||||
], ['server']);
|
||||
|
||||
api.export([
|
||||
|
||||
]);
|
||||
});
|
8
packages/telescope-newsletter/smart.json
Normal file
8
packages/telescope-newsletter/smart.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "telescope-newsletter",
|
||||
"description": "Telescope Newsletter package.",
|
||||
"homepage": "https://github.com/TelescopeJS/telescope-newsletter",
|
||||
"author": "Sacha Greif <info@sachagreif.com> (http://sachagreif.com)",
|
||||
"version": "0.1.9",
|
||||
"git": "https://github.com/TelescopeJS/telescope-newsletter.git"
|
||||
}
|
195
packages/telescope-newsletter/versions.json
Normal file
195
packages/telescope-newsletter/versions.json
Normal file
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"dependencies": [
|
||||
[
|
||||
"aldeed:simple-schema",
|
||||
"0.7.0"
|
||||
],
|
||||
[
|
||||
"application-configuration",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"binary-heap",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"blaze",
|
||||
"1.0.3"
|
||||
],
|
||||
[
|
||||
"blaze-tools",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"callback-hook",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"check",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"cmather:handlebars-server",
|
||||
"0.2.0"
|
||||
],
|
||||
[
|
||||
"deps",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"ejson",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"follower-livedata",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"geojson-utils",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"handlebars",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"html-tools",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"htmljs",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"id-map",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"iron:core",
|
||||
"0.3.2"
|
||||
],
|
||||
[
|
||||
"iron:dynamic-template",
|
||||
"0.3.0"
|
||||
],
|
||||
[
|
||||
"iron:layout",
|
||||
"0.3.0"
|
||||
],
|
||||
[
|
||||
"iron:router",
|
||||
"0.9.1"
|
||||
],
|
||||
[
|
||||
"jquery",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"json",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"livedata",
|
||||
"1.0.7"
|
||||
],
|
||||
[
|
||||
"logging",
|
||||
"1.0.2"
|
||||
],
|
||||
[
|
||||
"meteor",
|
||||
"1.0.2"
|
||||
],
|
||||
[
|
||||
"meteorhacks:npm",
|
||||
"1.1.2"
|
||||
],
|
||||
[
|
||||
"minifiers",
|
||||
"1.0.2"
|
||||
],
|
||||
[
|
||||
"minimongo",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"mongo-livedata",
|
||||
"1.0.3"
|
||||
],
|
||||
[
|
||||
"mrt:cookies",
|
||||
"0.3.0"
|
||||
],
|
||||
[
|
||||
"mrt:mailchimp",
|
||||
"0.4.0"
|
||||
],
|
||||
[
|
||||
"observe-sequence",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"ordered-dict",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"percolatestudio:synced-cron",
|
||||
"0.1.1"
|
||||
],
|
||||
[
|
||||
"random",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"reactive-dict",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"retry",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"routepolicy",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"spacebars",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"spacebars-compiler",
|
||||
"1.0.1"
|
||||
],
|
||||
[
|
||||
"telescope-base",
|
||||
"0.0.0"
|
||||
],
|
||||
[
|
||||
"telescope-i18n",
|
||||
"0.0.0"
|
||||
],
|
||||
[
|
||||
"telescope-lib",
|
||||
"0.2.9"
|
||||
],
|
||||
[
|
||||
"templating",
|
||||
"1.0.4"
|
||||
],
|
||||
[
|
||||
"ui",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"underscore",
|
||||
"1.0.0"
|
||||
],
|
||||
[
|
||||
"webapp",
|
||||
"1.0.2"
|
||||
]
|
||||
],
|
||||
"pluginDependencies": [],
|
||||
"toolVersion": "meteor-tool@1.0.26",
|
||||
"format": "1.0"
|
||||
}
|
Loading…
Add table
Reference in a new issue