forked from tomwasd/flow-router-seo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
flow-router-seo.js
159 lines (129 loc) · 5.69 KB
/
flow-router-seo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
FlowRouterSEO = function(config) {
var self = this;
if (config) {
if (config.database) {
// Allow for custom collection name;
if (config.databaseName) self.routes = new Mongo.Collection(config.databaseName);
else self.routes = new Mongo.Collection('flow-router-seo-routes');
if (Meteor.isServer) {
// Create an index to make lookups faster
// Only available server side
self.routes._ensureIndex('routeName');
}
self._subscription = null;
}
if (config.defaults) {
self.setDefaults(config.defaults);
}
}
self._updateDOM = function(settings) {
// Fill in any gaps with the defaults
// We use jQuery's extend as it is recursive
// (i.e. it performs a deep copy), unlike UnderscoreJS
settings = $.extend(true, {}, self._defaults, settings);
// Remove all existing meta tags that this package has added
$('head meta[data-flow-router-seo="true"]').remove();
$('head link[data-flow-router-seo="true"]').remove();
// Set title if specified otherwise use the default empty string title
document.title = settings.title;
// Set the description before the other meta tags as
// other tags are generated from the meta description
if (settings.description) $('head').append('<meta name="description" content="' + self._escapeHTML(settings.description) + '" data-flow-router-seo="true" />');
// Set canonical url tag
if (settings.addCanonical || settings.canonicalUrl) {
$('head').append('<link rel="canonical" content="' + (settings.canonicalUrl ? settings.canonicalUrl : self._currentUrl()) + '" data-flow-router-seo="true" />');
}
// Set meta tags
_.each(settings.meta, function(value, key) {
if (typeof value === 'function') value = value(settings);
if (value) $('head').append('<meta ' + key + 'content="' + self._escapeHTML(value) + '" data-flow-router-seo="true" />');
});
};
self._currentUrl = function() {
var currentRoute = FlowRouter.current();
var routeName = currentRoute.route.name;
// FlowRouter.path() returns a path starting with a '/' but Meteor.absoluteUrl()
// doesn't want it - that's why we've got the substr(1)
return Meteor.absoluteUrl(FlowRouter.path(routeName, currentRoute.params).substr(1));
};
self._currentTitle = function() {
return document.title;
};
self._currentDescription = function(settings) {
return settings.description || self._defaults.description;
};
// We need to escape any HTML within the title/meta tags
self._escapeHTML = function(HTML) {
return HTML.replace(/'/g, ''').replace(/"/g, '"');
};
// Internal function only called by flow router's enter trigger
self._set = function() {
// Either we want the defaults because the database isn't enabled
// or we want to start with the defaults until the subscription
// is ready and we have the route specific settings
self._updateDOM(self._defaults);
// If the database is enabled, see if there is an entry for this route
if (self.routes) {
var currentRoute = FlowRouter.current();
var routeName = currentRoute.route.name;
// Stop any existing subscription so that we don't end up
// with loads of subscriptions running at once
if (self._subscription) self._subscription.stop();
// If no route name specified then rely on defaults
if (routeName) {
// Subscribe by route name to see if we have SEO data stored
// for this route
self._subscription = Meteor.subscribe('flowRouterSEO', routeName, {onReady: function() {
// When the subscription is ready, see if we have a result
// If so, update the DOM with the settings otherwise rely on
// the defaults set above
var routeSettings = self.routes.findOne({routeName: routeName}, {fields: {_id: 0}});
// Only update the DOM if we're still on the right route
// (during the time it took to callback this function we might
// have changed routes)
currentRoute = FlowRouter.current();
if (routeSettings && routeName === currentRoute.route.name)
self._updateDOM(routeSettings);
}});
}
}
};
// The user can call this from the template's onCreated function
self.set = function(settings) {
// If the user has specified settings, update the DOM
if (settings) self._updateDOM(settings);
};
// The user can call this to initialise the router with defaults
// The defaults are merged rather than overridden
// For convenience, it can be called from a file both on the server & client
// but it only does stuff if called from the client (plus jQuery isn't on the
// server!)
self.setDefaults = function(settings) {
// We use jQuery's extend as it is recursive
// (i.e. it performs a deep copy), unlike UnderscoreJS
if (Meteor.isClient) self._defaults = $.extend(true, {}, self._defaults, settings);
};
// Default settings if nothing specified by the user
self._defaults = {
title: '',
description: '',
meta: {
'name="twitter:title"': self._currentTitle,
'name="twitter:url"': self._currentUrl,
'name="twitter:description"': self._currentDescription,
'property="og:title"': self._currentTitle,
'property="og:url"': self._currentUrl,
'property="og:description"': self._currentDescription,
}
};
// Whenever we enter a route, set the correct title and meta tags
FlowRouter.triggers.enter([self._set]);
if (Meteor.isServer) {
if (self.routes) {
Meteor.publish('flowRouterSEO', function(routeName) {
check(routeName, String);
return self.routes.find({routeName: routeName});
});
}
}
};