prismic.io
Version:
JavaScript development kit for prismic.io
1,681 lines (1,508 loc) • 356 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var Requests = require('./requests'),
Cookies = require('./cookies'),
documents = require('./documents'),
ApiCache = require('./cache'),
Predicates = require('./predicates'),
experiments = require('./experiments');
var Experiments = experiments.Experiments,
Document = documents.Document;
var experimentCookie = "io.prismic.experiment",
previewCookie = "io.prismic.preview";
/**
* Initialisation of the API object.
* This is for internal use, from outside this kit, you should call Prismic.Api()
* @private
*/
function Api(url, options) {
var opts = options || {};
this.accessToken = opts.accessToken;
this.url = url + (this.accessToken ? (url.indexOf('?') > -1 ? '&' : '?') + 'access_token=' + this.accessToken : '');
this.req = opts.req;
this.apiCache = opts.apiCache || globalCache();
this.requestHandler = opts.requestHandler || Requests.request;
this.apiCacheKey = this.url + (this.accessToken ? '#' + this.accessToken : '');
this.apiDataTTL = opts.apiDataTTL || 5;
return this;
}
Api.prototype = {
// Predicates
AT: "at",
ANY: "any",
SIMILAR: "similar",
FULLTEXT: "fulltext",
NUMBER: {
GT: "number.gt",
LT: "number.lt"
},
DATE: {
// Other date operators are available: see the documentation.
AFTER: "date.after",
BEFORE: "date.before",
BETWEEN: "date.between"
},
// Fragment: usable as the second element of a query array on most predicates (except SIMILAR).
// You can also use "my.*" for your custom fields.
DOCUMENT: {
ID: "document.id",
TYPE: "document.type",
TAGS: "document.tags"
},
data: null,
/**
* Fetches data used to construct the api client, from cache if it's
* present, otherwise from calling the prismic api endpoint (which is
* then cached).
*
* @param {function} callback - Callback to receive the data. Optional, you can use the promise result.
* @returns {Promise} Promise holding the data or error
*/
get: function get(callback) {
var self = this;
var cacheKey = this.apiCacheKey;
return new Promise(function (resolve, reject) {
var cb = function cb(err, value, xhr, ttl) {
if (callback) callback(err, value, xhr, ttl);
if (value) resolve(value);
if (err) reject(err);
};
self.apiCache.get(cacheKey, function (err, value) {
if (err || value) {
cb(err, value);
return;
}
self.requestHandler(self.url, function (err, data, xhr, ttl) {
if (err) {
cb(err, null, xhr, ttl);
return;
}
var parsed = self.parse(data);
ttl = ttl || self.apiDataTTL;
self.apiCache.set(cacheKey, parsed, ttl, function (err) {
cb(err, parsed, xhr, ttl);
});
});
});
});
},
/**
* Cleans api data from the cache and fetches an up to date copy.
*
* @param {function} callback - Optional callback function that is called after the data has been refreshed
* @returns {Promise}
*/
refresh: function refresh(callback) {
var self = this;
var cacheKey = this.apiCacheKey;
return new Promise(function (resolve, reject) {
var cb = function cb(err, value, xhr) {
if (callback) callback(err, value, xhr);
if (value) resolve(value);
if (err) reject(err);
};
self.apiCache.remove(cacheKey, function (err) {
if (err) {
cb(err);return;
}
self.get(function (err, data) {
if (err) {
cb(err);return;
}
self.data = data;
self.bookmarks = data.bookmarks;
self.experiments = new Experiments(data.experiments);
cb();
});
});
});
},
/**
* Parses and returns the /api document.
* This is for internal use, from outside this kit, you should call Prismic.Api()
*
* @param {string} data - The JSON document responded on the API's endpoint
* @returns {Api} - The Api object that can be manipulated
* @private
*/
parse: function parse(data) {
var refs,
master,
forms = {},
form,
types,
tags,
f,
i;
// Parse the forms
for (i in data.forms) {
if (data.forms.hasOwnProperty(i)) {
f = data.forms[i];
if (this.accessToken) {
f.fields['access_token'] = {};
f.fields['access_token']['type'] = 'string';
f.fields['access_token']['default'] = this.accessToken;
}
form = new Form(f.name, f.fields, f.form_method, f.rel, f.enctype, f.action);
forms[i] = form;
}
}
refs = data.refs.map(function (r) {
return new Ref(r.ref, r.label, r.isMasterRef, r.scheduledAt, r.id);
}) || [];
master = refs.filter(function (r) {
return r.isMaster === true;
});
types = data.types;
tags = data.tags;
if (master.length === 0) {
throw "No master ref.";
}
return {
bookmarks: data.bookmarks || {},
refs: refs,
forms: forms,
master: master[0],
types: types,
tags: tags,
experiments: data.experiments,
oauthInitiate: data['oauth_initiate'],
oauthToken: data['oauth_token'],
quickRoutes: data.quickRoutes
};
},
/**
* @deprecated use form() now
* @param {string} formId - The id of a form, like "everything", or "products"
* @returns {SearchForm} - the SearchForm that can be used.
*/
forms: function forms(formId) {
return this.form(formId);
},
/**
* Returns a useable form from its id, as described in the RESTful description of the API.
* For instance: api.form("everything") works on every repository (as "everything" exists by default)
* You can then chain the calls: api.form("everything").query('[[:d = at(document.id, "UkL0gMuvzYUANCpf")]]').ref(ref).submit()
*
* @param {string} formId - The id of a form, like "everything", or "products"
* @returns {SearchForm} - the SearchForm that can be used.
*/
form: function form(formId) {
var form = this.data.forms[formId];
if (form) {
return new SearchForm(this, form, {});
}
return null;
},
/**
* The ID of the master ref on this prismic.io API.
* Do not use like this: searchForm.ref(api.master()).
* Instead, set your ref once in a variable, and call it when you need it; this will allow to change the ref you're viewing easily for your entire page.
*
* @returns {string}
*/
master: function master() {
return this.data.master.ref;
},
/**
* Returns the ref ID for a given ref's label.
* Do not use like this: searchForm.ref(api.ref("Future release label")).
* Instead, set your ref once in a variable, and call it when you need it; this will allow to change the ref you're viewing easily for your entire page.
*
* @param {string} label - the ref's label
* @returns {string}
*/
ref: function ref(label) {
for (var i = 0; i < this.data.refs.length; i++) {
if (this.data.refs[i].label == label) {
return this.data.refs[i].ref;
}
}
return null;
},
/**
* The current experiment, or null
* @returns {Experiment}
*/
currentExperiment: function currentExperiment() {
return this.experiments.current();
},
quickRoutesEnabled: function quickRoutesEnabled() {
return this.data.quickRoutes.enabled;
},
/**
* Retrieve quick routes definitions
*/
quickRoutes: function quickRoutes(callback) {
var self = this;
return new Promise(function (resolve, reject) {
self.requestHandler(self.data.quickRoutes.url, function (err, data, xhr) {
if (callback) callback(err, data, xhr);
if (err) reject(err);
if (data) resolve(data);
});
});
},
/**
* Query the repository
* @param {string|array|Predicate} the query itself
* @param {object} additional parameters. In NodeJS, pass the request as 'req'.
* @param {function} callback(err, response)
*/
query: function query(q, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
var opts = options || {};
var form = this.form('everything');
for (var key in opts) {
form = form.set(key, options[key]);
}
// Don't override the ref if the caller specified one in the options
if (!opts['ref']) {
// Look in cookies if we have a ref (preview or experiment)
var cookieString = '';
if (this.req) {
// NodeJS
cookieString = this.req.headers["cookie"] || '';
} else if (typeof window !== 'undefined') {
// Browser
cookieString = window.document.cookie || '';
}
var cookies = Cookies.parse(cookieString);
var previewRef = cookies[previewCookie];
var experimentRef = this.experiments.refFromCookie(cookies[experimentCookie]);
form = form.ref(previewRef || experimentRef || this.master());
}
if (q) {
form.query(q);
}
return form.submit(callback);
},
/**
* Retrieve the document returned by the given query
* @param {string|array|Predicate} the query
* @param {object} additional parameters. In NodeJS, pass the request as 'req'.
* @param {function} callback(err, doc)
*/
queryFirst: function queryFirst(q, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
var opts = {};
for (var key in options || {}) {
opts[key] = options[key];
}
opts.page = 1;
opts.pageSize = 1;
return this.query(q, opts, function (err, response) {
if (callback) {
var result = response && response.results && response.results[0];
callback(err, result);
}
}).then(function (response) {
return response && response.results && response.results[0];
});
},
/**
* Retrieve the document with the given id
* @param {string} id
* @param {object} additional parameters
* @param {function} callback(err, doc)
*/
getByID: function getByID(id, options, callback) {
options = options || {};
if (!options.lang) options.lang = '*';
return this.queryFirst(Predicates.at('document.id', id), options, callback);
},
/**
* Retrieve multiple documents from an array of id
* @param {array} ids
* @param {object} additional parameters
* @param {function} callback(err, response)
*/
getByIDs: function getByIDs(ids, options, callback) {
options = options || {};
if (!options.lang) options.lang = '*';
return this.query(['in', 'document.id', ids], options, callback);
},
/**
* Retrieve the document with the given uid
* @param {string} type the custom type of the document
* @param {string} uid
* @param {object} additional parameters
* @param {function} callback(err, response)
*/
getByUID: function getByUID(type, uid, options, callback) {
options = options || {};
if (!options.lang) options.lang = '*';
return this.queryFirst(Predicates.at('my.' + type + '.uid', uid), options, callback);
},
/**
* Retrieve the singleton document with the given type
* @param {string} type the custom type of the document
* @param {object} additional parameters
* @param {function} callback(err, response)
*/
getSingle: function getSingle(type, options, callback) {
return this.queryFirst(Predicates.at('document.type', type), options, callback);
},
/**
* Retrieve the document with the given bookmark
* @param {string} bookmark name
* @param {object} additional parameters
* @param {function} callback(err, response)
* @returns {Promise}
*/
getBookmark: function getBookmark(bookmark, options, callback) {
return new Promise(function (resolve, reject) {
var id = this.bookmarks[bookmark];
if (id) {
resolve(id);
} else {
var err = new Error("Error retrieving bookmarked id");
if (callback) callback(err);
reject(err);
}
}).then(function (id) {
return this.getByID(id, options, callback);
});
},
/**
* Return the URL to display a given preview
* @param {string} token as received from Prismic server to identify the content to preview
* @param {function} linkResolver the link resolver to build URL for your site
* @param {string} defaultUrl the URL to default to return if the preview doesn't correspond to a document
* (usually the home page of your site)
* @param {function} callback to get the resulting URL (optional, you can get it from the Promise result)
* @returns {Promise}
*/
previewSession: function previewSession(token, linkResolver, defaultUrl, callback) {
var api = this;
return new Promise(function (resolve, reject) {
var cb = function cb(err, value, xhr) {
if (callback) callback(err, value, xhr);
if (err) {
reject(err);
} else {
resolve(value);
}
};
api.requestHandler(token, function (err, result, xhr) {
if (err) {
cb(err, defaultUrl, xhr);
return;
}
try {
var mainDocumentId = result.mainDocument;
if (!mainDocumentId) {
cb(null, defaultUrl, xhr);
} else {
api.form("everything").query(Predicates.at("document.id", mainDocumentId)).ref(token).lang('*').submit(function (err, response) {
if (err) {
cb(err);
}
try {
if (response.results.length === 0) {
cb(null, defaultUrl, xhr);
} else {
cb(null, linkResolver(response.results[0]), xhr);
}
} catch (e) {
cb(e);
}
});
}
} catch (e) {
cb(e, defaultUrl, xhr);
}
});
});
},
/**
* Fetch a URL corresponding to a query, and parse the response as a Response object
*/
request: function request(url, callback) {
var api = this;
var cacheKey = url + (this.accessToken ? '#' + this.accessToken : '');
var cache = this.apiCache;
function run(cb) {
cache.get(cacheKey, function (err, value) {
if (err || value) {
cb(err, api.response(value));
return;
}
api.requestHandler(url, function (err, documents, xhr, ttl) {
if (err) {
cb(err, null, xhr);
return;
}
if (ttl) {
cache.set(cacheKey, documents, ttl, function (err) {
cb(err, api.response(documents));
});
} else {
cb(null, api.response(documents));
}
});
});
}
return new Promise(function (resolve, reject) {
run(function (err, value, xhr) {
if (callback) callback(err, value, xhr);
if (err) reject(err);
if (value) resolve(value);
});
});
},
getNextPage: function getNextPage(nextPage, callback) {
return this.request(nextPage + (this.accessToken ? '&access_token=' + this.accessToken : ''), callback);
},
/**
* JSON documents to Response object
*/
response: function response(documents) {
var results = documents.results.map(parseDoc);
return new Response(documents.page, documents.results_per_page, documents.results_size, documents.total_results_size, documents.total_pages, documents.next_page, documents.prev_page, results || []);
}
};
/**
* Embodies a submittable RESTful form as described on the API endpoint (as per RESTful standards)
* @constructor
* @private
*/
function Form(name, fields, form_method, rel, enctype, action) {
this.name = name;
this.fields = fields;
this.form_method = form_method;
this.rel = rel;
this.enctype = enctype;
this.action = action;
}
Form.prototype = {};
/**
* Parse json as a document
*
* @returns {Document}
*/
var parseDoc = function parseDoc(json) {
var fragments = {};
for (var field in json.data[json.type]) {
fragments[json.type + '.' + field] = json.data[json.type][field];
}
var slugs = [];
if (json.slugs !== undefined) {
for (var i = 0; i < json.slugs.length; i++) {
slugs.push(decodeURIComponent(json.slugs[i]));
}
}
return new Document(json.id, json.uid || null, json.type, json.href, json.tags, slugs, json.first_publication_date, json.last_publication_date, json.lang, json.alternate_languages, fragments, json.data);
};
/**
* Embodies a SearchForm object. To create SearchForm objects that are allowed in the API, please use the API.form() method.
* @constructor
* @global
* @alias SearchForm
*/
function SearchForm(api, form, data) {
this.api = api;
this.form = form;
this.data = data || {};
for (var field in form.fields) {
if (form.fields[field]['default']) {
this.data[field] = [form.fields[field]['default']];
}
}
}
SearchForm.prototype = {
/**
* Set an API call parameter. This will only work if field is a valid field of the
* RESTful form in the first place (as described in the /api document); otherwise,
* an "Unknown field" error is thrown.
* Please prefer using dedicated methods like query(), orderings(), ...
*
* @param {string} field - The name of the field to set
* @param {string} value - The value that gets assigned
* @returns {SearchForm} - The SearchForm itself
*/
set: function set(field, value) {
var fieldDesc = this.form.fields[field];
if (!fieldDesc) throw new Error("Unknown field " + field);
var values = this.data[field] || [];
if (value === '' || value === undefined) {
// we must compare value to null because we want to allow 0
value = null;
}
if (fieldDesc.multiple) {
if (value) values.push(value);
} else {
values = value && [value];
}
this.data[field] = values;
return this;
},
/**
* Sets a ref to query on for this SearchForm. This is a mandatory
* method to call before calling submit(), and api.form('everything').submit()
* will not work.
*
* @param {Ref} ref - The Ref object defining the ref to query
* @returns {SearchForm} - The SearchForm itself
*/
ref: function ref(_ref) {
return this.set("ref", _ref);
},
/**
* Sets a predicate-based query for this SearchForm. This is where you
* paste what you compose in your prismic.io API browser.
*
* @example form.query(Prismic.Predicates.at("document.id", "foobar"))
* @param {string|...array} query - Either a query as a string, or as many predicates as you want. See Prismic.Predicates.
* @returns {SearchForm} - The SearchForm itself
*/
query: function query(_query) {
if (typeof _query === 'string') {
return this.set("q", _query);
} else {
var predicates;
if (_query.constructor === Array && _query.length > 0 && _query[0].constructor === Array) {
predicates = _query;
} else {
predicates = [].slice.apply(arguments); // Convert to a real JS array
}
var stringQueries = [];
predicates.forEach(function (predicate) {
stringQueries.push(Predicates.toQuery(predicate));
});
return this.query("[" + stringQueries.join("") + "]");
}
},
/**
* Sets a page size to query for this SearchForm. This is an optional method.
*
* @param {number} size - The page size
* @returns {SearchForm} - The SearchForm itself
*/
pageSize: function pageSize(size) {
return this.set("pageSize", size);
},
/**
* Restrict the results document to the specified fields
*
* @param {string|array} fields - The list of fields, array or comma separated string
* @returns {SearchForm} - The SearchForm itself
*/
fetch: function fetch(fields) {
if (fields instanceof Array) {
fields = fields.join(",");
}
return this.set("fetch", fields);
},
/**
* Include the requested fields in the DocumentLink instances in the result
*
* @param {string|array} fields - The list of fields, array or comma separated string
* @returns {SearchForm} - The SearchForm itself
*/
fetchLinks: function fetchLinks(fields) {
if (fields instanceof Array) {
fields = fields.join(",");
}
return this.set("fetchLinks", fields);
},
/**
* Sets the language to query for this SearchForm. This is an optional method.
*
* @param {string} fields - The language code
* @returns {SearchForm} - The SearchForm itself
*/
lang: function lang(fields) {
return this.set("lang", fields);
},
/**
* Sets the page number to query for this SearchForm. This is an optional method.
*
* @param {number} p - The page number
* @returns {SearchForm} - The SearchForm itself
*/
page: function page(p) {
return this.set("page", p);
},
/**
* Sets the orderings to query for this SearchForm. This is an optional method.
*
* @param {array} orderings - Array of string: list of fields, optionally followed by space and desc. Example: ['my.product.price desc', 'my.product.date']
* @returns {SearchForm} - The SearchForm itself
*/
orderings: function orderings(_orderings) {
if (typeof _orderings === 'string') {
// Backward compatibility
return this.set("orderings", _orderings);
} else if (!_orderings) {
// Noop
return this;
} else {
// Normal usage
return this.set("orderings", "[" + _orderings.join(",") + "]");
}
},
/**
* Submits the query, and calls the callback function.
*
* @param {function} callback - Optional callback function that is called after the query was made,
* to which you may pass three parameters: a potential error (null if no problem),
* a Response object (containing all the pagination specifics + the array of Docs),
* and the XMLHttpRequest
*/
submit: function submit(callback) {
var self = this;
var url = this.form.action;
if (this.data) {
var sep = url.indexOf('?') > -1 ? '&' : '?';
for (var key in this.data) {
if (this.data.hasOwnProperty(key)) {
var values = this.data[key];
if (values) {
for (var i = 0; i < values.length; i++) {
url += sep + key + '=' + encodeURIComponent(values[i]);
sep = '&';
}
}
}
}
}
return self.api.request(url, callback);
}
};
/**
* Embodies the response of a SearchForm query as returned by the API.
* It includes all the fields that are useful for pagination (page, total_pages, total_results_size, ...),
* as well as the field "results", which is an array of {@link Document} objects, the documents themselves.
*
* @constructor
* @global
*/
function Response(page, results_per_page, results_size, total_results_size, total_pages, next_page, prev_page, results) {
/**
* The current page
* @type {number}
*/
this.page = page;
/**
* The number of results per page
* @type {number}
*/
this.results_per_page = results_per_page;
/**
* The size of the current page
* @type {number}
*/
this.results_size = results_size;
/**
* The total size of results across all pages
* @type {number}
*/
this.total_results_size = total_results_size;
/**
* The total number of pages
* @type {number}
*/
this.total_pages = total_pages;
/**
* The URL of the next page in the API
* @type {string}
*/
this.next_page = next_page;
/**
* The URL of the previous page in the API
* @type {string}
*/
this.prev_page = prev_page;
/**
* Array of {@link Document} for the current page
* @type {Array}
*/
this.results = results;
}
/**
* Embodies a prismic.io ref (a past or future point in time you can query)
* @constructor
* @global
*/
function Ref(ref, label, isMaster, scheduledAt, id) {
/**
* @field
* @description the ID of the ref
*/
this.ref = ref;
/**
* @field
* @description the label of the ref
*/
this.label = label;
/**
* @field
* @description is true if the ref is the master ref
*/
this.isMaster = isMaster;
/**
* @field
* @description the scheduled date of the ref
*/
this.scheduledAt = scheduledAt;
/**
* @field
* @description the name of the ref
*/
this.id = id;
}
Ref.prototype = {};
function globalCache() {
var g;
if ((typeof global === 'undefined' ? 'undefined' : _typeof(global)) == 'object') {
g = global; // NodeJS
} else {
g = window; // browser
}
if (!g.prismicCache) {
g.prismicCache = new ApiCache();
}
return g.prismicCache;
}
module.exports = {
experimentCookie: experimentCookie,
previewCookie: previewCookie,
Api: Api,
Form: Form,
SearchForm: SearchForm,
Ref: Ref,
parseDoc: parseDoc
};
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./cache":3,"./cookies":4,"./documents":5,"./experiments":6,"./predicates":9,"./requests":12}],2:[function(require,module,exports){
'use strict';
// IE below 12 doesn't support promises
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
require('es6-promise').polyfill();
// Polyfill for inheritance
if (typeof Object.create != 'function') {
Object.create = function () {
var Object = function Object() {};
return function (prototype) {
if (arguments.length > 1) {
throw Error('Second argument not supported');
}
if ((typeof prototype === 'undefined' ? 'undefined' : _typeof(prototype)) != 'object') {
throw TypeError('Argument must be an object');
}
Object.prototype = prototype;
var result = {};
Object.prototype = null;
return result;
};
}();
}
window.Prismic = require('./prismic');
},{"./prismic":10,"es6-promise":20}],3:[function(require,module,exports){
"use strict";
var LRUCache = require('./lru');
/**
* Api cache
*/
function ApiCache(limit) {
this.lru = new LRUCache(limit);
}
ApiCache.prototype = {
get: function get(key, cb) {
var maybeEntry = this.lru.get(key);
if (maybeEntry && !this.isExpired(key)) {
return cb(null, maybeEntry.data);
}
return cb();
},
set: function set(key, value, ttl, cb) {
this.lru.remove(key);
this.lru.put(key, {
data: value,
expiredIn: ttl ? Date.now() + ttl * 1000 : 0
});
return cb();
},
isExpired: function isExpired(key) {
var entry = this.lru.get(key);
if (entry) {
return entry.expiredIn !== 0 && entry.expiredIn < Date.now();
} else {
return false;
}
},
remove: function remove(key, cb) {
this.lru.remove(key);
return cb();
},
clear: function clear(cb) {
this.lru.removeAll();
return cb();
}
};
module.exports = ApiCache;
},{"./lru":8}],4:[function(require,module,exports){
"use strict";
// Some portions of code from https://github.com/jshttp/cookie
var decode = decodeURIComponent;
function tryDecode(str, decode) {
try {
return decode(str);
} catch (e) {
return str;
}
}
function parse(str, options) {
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string');
}
var obj = {};
var opt = options || {};
var pairs = str.split(/; */);
var dec = opt.decode || decode;
pairs.forEach(function (pair) {
var eq_idx = pair.indexOf('=');
// skip things that don't look like key=value
if (eq_idx < 0) {
return;
}
var key = pair.substr(0, eq_idx).trim();
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
obj[key] = tryDecode(val, dec);
}
});
return obj;
}
module.exports = {
parse: parse
};
},{}],5:[function(require,module,exports){
"use strict";
var DateUtils = require('./utils/date');
/**
* Functions to access fragments: superclass for Document and Doc (from Group), not supposed to be created directly
* @constructor
*/
function WithFragments() {}
WithFragments.prototype = {
/**
* Gets the fragment in the current Document object. Since you most likely know the type
* of this fragment, it is advised that you use a dedicated method, like get StructuredText() or getDate(),
* for instance.
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.author"
* @returns {object} - The JavaScript Fragment object to manipulate
*/
get: function get(name) {
var frags = this._getFragments(name);
return frags.length ? frags[0] : null;
},
/**
* Builds an array of all the fragments in case they are multiple.
*
* @param {string} name - The name of the multiple fragment to get, with its type; for instance, "blog-post.author"
* @returns {array} - An array of each JavaScript fragment object to manipulate.
*/
getAll: function getAll(name) {
return this._getFragments(name);
},
/**
* Gets the image fragment in the current Document object, for further manipulation.
*
* @example document.getImage('blog-post.photo').asHtml(linkResolver)
*
* @param {string} fragment - The name of the fragment to get, with its type; for instance, "blog-post.photo"
* @returns {ImageEl} - The Image object to manipulate
*/
getImage: function getImage(fragment) {
var Fragments = require('./fragments');
var img = this.get(fragment);
if (img instanceof Fragments.Image) {
return img;
}
if (img instanceof Fragments.StructuredText) {
// find first image in st.
return img;
}
return null;
},
// Useful for obsolete multiples
getAllImages: function getAllImages(fragment) {
var Fragments = require('./fragments');
var images = this.getAll(fragment);
return images.map(function (image) {
if (image instanceof Fragments.Image) {
return image;
}
if (image instanceof Fragments.StructuredText) {
throw new Error("Not done.");
}
return null;
});
},
getFirstImage: function getFirstImage() {
var Fragments = require('./fragments');
var fragments = this.fragments;
var firstImage = Object.keys(fragments).reduce(function (image, key) {
if (image) {
return image;
} else {
var element = fragments[key];
if (typeof element.getFirstImage === "function") {
return element.getFirstImage();
} else if (element instanceof Fragments.Image) {
return element;
} else return null;
}
}, null);
return firstImage;
},
getFirstTitle: function getFirstTitle() {
var Fragments = require('./fragments');
var fragments = this.fragments;
var firstTitle = Object.keys(fragments).reduce(function (st, key) {
if (st) {
return st;
} else {
var element = fragments[key];
if (typeof element.getFirstTitle === "function") {
return element.getFirstTitle();
} else if (element instanceof Fragments.StructuredText) {
return element.getTitle();
} else return null;
}
}, null);
return firstTitle;
},
getFirstParagraph: function getFirstParagraph() {
var fragments = this.fragments;
var firstParagraph = Object.keys(fragments).reduce(function (st, key) {
if (st) {
return st;
} else {
var element = fragments[key];
if (typeof element.getFirstParagraph === "function") {
return element.getFirstParagraph();
} else return null;
}
}, null);
return firstParagraph;
},
/**
* Gets the view within the image fragment in the current Document object, for further manipulation.
*
* @example document.getImageView('blog-post.photo', 'large').asHtml(linkResolver)
*
* @param {string} name- The name of the fragment to get, with its type; for instance, "blog-post.photo"
* @returns {ImageView} view - The View object to manipulate
*/
getImageView: function getImageView(name, view) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.Image) {
return fragment.getView(view);
}
if (fragment instanceof Fragments.StructuredText) {
for (var i = 0; i < fragment.blocks.length; i++) {
if (fragment.blocks[i].type == 'image') {
return fragment.blocks[i];
}
}
}
return null;
},
// Useful for obsolete multiples
getAllImageViews: function getAllImageViews(name, view) {
return this.getAllImages(name).map(function (image) {
return image.getView(view);
});
},
/**
* Gets the timestamp fragment in the current Document object, for further manipulation.
*
* @example document.getDate('blog-post.publicationdate').asHtml(linkResolver)
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.publicationdate"
* @returns {Date} - The Date object to manipulate
*/
getTimestamp: function getTimestamp(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.Timestamp) {
return fragment.value;
}
return null;
},
/**
* Gets the date fragment in the current Document object, for further manipulation.
*
* @example document.getDate('blog-post.publicationdate').asHtml(linkResolver)
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.publicationdate"
* @returns {Date} - The Date object to manipulate
*/
getDate: function getDate(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.Date) {
return fragment.value;
}
return null;
},
/**
* Gets a boolean value of the fragment in the current Document object, for further manipulation.
* This works great with a Select fragment. The Select values that are considered true are (lowercased before matching): 'yes', 'on', and 'true'.
*
* @example if(document.getBoolean('blog-post.enableComments')) { ... }
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.enableComments"
* @returns {boolean} - The boolean value of the fragment
*/
getBoolean: function getBoolean(name) {
var fragment = this.get(name);
return fragment.value && (fragment.value.toLowerCase() == 'yes' || fragment.value.toLowerCase() == 'on' || fragment.value.toLowerCase() == 'true');
},
/**
* Gets the text fragment in the current Document object, for further manipulation.
* The method works with StructuredText fragments, Text fragments, Number fragments, Select fragments and Color fragments.
*
* @example document.getText('blog-post.label').asHtml(linkResolver).
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.label"
* @param {string} after - a suffix that will be appended to the value
* @returns {object} - either StructuredText, or Text, or Number, or Select, or Color.
*/
getText: function getText(name, after) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.StructuredText) {
return fragment.blocks.map(function (block) {
if (block.text) {
return block.text + (after ? after : '');
}
return '';
}).join('\n');
}
if (fragment instanceof Fragments.Text) {
if (fragment.value) {
return fragment.value + (after ? after : '');
}
}
if (fragment instanceof Fragments.Number) {
if (fragment.value) {
return fragment.value + (after ? after : '');
}
}
if (fragment instanceof Fragments.Select) {
if (fragment.value) {
return fragment.value + (after ? after : '');
}
}
if (fragment instanceof Fragments.Color) {
if (fragment.value) {
return fragment.value + (after ? after : '');
}
}
return null;
},
/**
* Gets the StructuredText fragment in the current Document object, for further manipulation.
* @example document.getStructuredText('blog-post.body').asHtml(linkResolver)
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.body"
* @returns {StructuredText} - The StructuredText fragment to manipulate.
*/
getStructuredText: function getStructuredText(name) {
var fragment = this.get(name);
if (fragment instanceof require('./fragments').StructuredText) {
return fragment;
}
return null;
},
/**
* Gets the Link fragment in the current Document object, for further manipulation.
* @example document.getLink('blog-post.link').url(resolver)
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.link"
* @returns {WebLink|DocumentLink|ImageLink} - The Link fragment to manipulate.
*/
getLink: function getLink(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.WebLink || fragment instanceof Fragments.DocumentLink || fragment instanceof Fragments.FileLink || fragment instanceof Fragments.ImageLink) {
return fragment;
}
return null;
},
/**
* Gets the Number fragment in the current Document object, for further manipulation.
* @example document.getNumber('product.price')
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "product.price"
* @returns {number} - The number value of the fragment.
*/
getNumber: function getNumber(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.Number) {
return fragment.value;
}
return null;
},
/**
* Gets the Color fragment in the current Document object, for further manipulation.
* @example document.getColor('product.color')
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "product.color"
* @returns {string} - The string value of the Color fragment.
*/
getColor: function getColor(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.Color) {
return fragment.value;
}
return null;
},
/** Gets the GeoPoint fragment in the current Document object, for further manipulation.
*
* @example document.getGeoPoint('blog-post.location').asHtml(linkResolver)
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.location"
* @returns {GeoPoint} - The GeoPoint object to manipulate
*/
getGeoPoint: function getGeoPoint(name) {
var Fragments = require('./fragments');
var fragment = this.get(name);
if (fragment instanceof Fragments.GeoPoint) {
return fragment;
}
return null;
},
/**
* Gets the Group fragment in the current Document object, for further manipulation.
*
* @example document.getGroup('product.gallery').asHtml(linkResolver).
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "product.gallery"
* @returns {Group} - The Group fragment to manipulate.
*/
getGroup: function getGroup(name) {
var fragment = this.get(name);
if (fragment instanceof require('./fragments').Group) {
return fragment;
}
return null;
},
/**
* Shortcut to get the HTML output of the fragment in the current document.
* This is the same as writing document.get(fragment).asHtml(linkResolver);
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "blog-post.body"
* @param {function} linkResolver
* @returns {string} - The HTML output
*/
getHtml: function getHtml(name, linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function linkResolver(doc, isBroken) {
return ctx.linkResolver(ctx, doc, isBroken);
};
}
var fragment = this.get(name);
if (fragment && fragment.asHtml) {
return fragment.asHtml(linkResolver);
}
return null;
},
/**
* Transforms the whole document as an HTML output. Each fragment is separated by a <section> tag,
* with the attribute data-field="nameoffragment"
* Note that most of the time you will not use this method, but read fragment independently and generate
* HTML output for {@link StructuredText} fragment with that class' asHtml method.
*
* @param {function} linkResolver
* @returns {string} - The HTML output
*/
asHtml: function asHtml(linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function linkResolver(doc, isBroken) {
return ctx.linkResolver(ctx, doc, isBroken);
};
}
var htmls = [];
for (var field in this.fragments) {
var fragment = this.get(field);
htmls.push(fragment && fragment.asHtml ? '<section data-field="' + field + '">' + fragment.asHtml(linkResolver) + '</section>' : '');
}
return htmls.join('');
},
/**
* Turns the document into a useable text version of it.
*
* @returns {string} - basic text version of the fragment
*/
asText: function asText(linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function linkResolver(doc, isBroken) {
return ctx.linkResolver(ctx, doc, isBroken);
};
}
var texts = [];
for (var field in this.fragments) {
var fragment = this.get(field);
texts.push(fragment && fragment.asText ? fragment.asText(linkResolver) : '');
}
return texts.join('');
},
/**
* Linked documents, as an array of {@link DocumentLink}
* @returns {Array}
*/
linkedDocuments: function linkedDocuments() {
var i, j, link;
var result = [];
var Fragments = require('./fragments');
for (var field in this.data) {
var fragment = this.get(field);
if (fragment instanceof Fragments.DocumentLink) {
result.push(fragment);
}
if (fragment instanceof Fragments.StructuredText) {
for (i = 0; i < fragment.blocks.length; i++) {
var block = fragment.blocks[i];
if (block.type == "image" && block.linkTo) {
link = Fragments.initField(block.linkTo);
if (link instanceof Fragments.DocumentLink) {
result.push(link);
}
}
var spans = block.spans || [];
for (j = 0; j < spans.length; j++) {
var span = spans[j];
if (span.type == "hyperlink") {
link = Fragments.initField(span.data);
if (link instanceof Fragments.DocumentLink) {
result.push(link);
}
}
}
}
}
if (fragment instanceof Fragments.Group) {
for (i = 0; i < fragment.value.length; i++) {
result = result.concat(fragment.value[i].linkedDocuments());
}
}
if (fragment instanceof Fragments.SliceZone) {
for (i = 0; i < fragment.value.length; i++) {
var slice = fragment.value[i];
if (slice.value instanceof Fragments.DocumentLink) {
result.push(slice.value);
}
}
}
}
return result;
},
/**
* An array of the fragments with the given fragment name.
* The array is often a single-element array, expect when the fragment is a multiple fragment.
* @private
*/
_getFragments: function _getFragments(name) {
if (!this.fragments || !this.fragments[name]) {
return [];
}
if (Array.isArray(this.fragments[name])) {
return this.fragments[name];
} else {
return [this.fragments[name]];
}
}
};
/**
* Embodies a document as returned by the API.
* Most useful fields: id, type, tags, slug, slugs
* @constructor
* @global
* @alias Doc
*/
function Document(id, uid, type, href, tags, slugs, firstPublicationDate, lastPublicationDate, lang, alternateLanguages, data, rawJSON) {
/**
* The ID of the document
* @type {string}
*/
this.id = id;
/**
* The User ID of the document, a human readable id
* @type {string|null}
*/
this.uid = uid;
/**
* The type of the document, corresponds to a document mask defined in the repository
* @type {string}
*/
this.type = type;
/**
* The URL of the document in the API
* @type {string}
*/
this.href = href;
/**
* The tags of the document
* @type {array}
*/
this.tags = tags;
/**
* The current slug of the document, "-" if none was provided
* @type {string}
*/
this.slug = slugs ? slugs[0] : "-";
/**
* All the slugs that were ever used by this document (including the current one, at the head)
* @type {array}
*/
this.slugs = slugs;
/**
* same as fragments
*/
this.data = data;
/**
* raw JSON from the API
*/
this.rawJSON = rawJSON;
/**
* The first publication date of the document
*/
this.firstPublicationDate = DateUtils.parse(firstPublicationDate);
/**
* The last publication date of the document
*/
this.lastPublicationDate = DateUtils.parse(lastPublicationDate);
/**
* The language code of the document
*/
this.lang = lang ? lang : null;
/**
* The alternate language versions of the document
*/
this.alternateLanguages = alternateLanguages ? alternateLanguages : [];
/**
* Fragments, converted to business objects
*/
this.fragments = require('./fragments').parseFragments(data);
}
Document.prototype = Object.create(WithFragments.prototype);
/**
* Gets the SliceZone fragment in the current Document object, for further manipulation.
*
* @example document.getSliceZone('product.gallery').asHtml(linkResolver).
*
* @param {string} name - The name of the fragment to get, with its type; for instance, "product.gallery"
* @returns {Group} - The SliceZone fragment to manipulate.
*/
Document.prototype.getSliceZone = function (name) {
var fragment = this.get(name);
if (fragment instanceof require('./fragments').SliceZone) {
return fragment;
}
return null;
};
function GroupDoc(data) {
/**
* The original JSON data from the API
*/
this.data = data;
/**
* Fragments, converted to business objects
*/
this.fragments = require('./fragments').parseFragments(data);
}
GroupDoc.prototype = Object.create(WithFragments.prototype);
// -- Private helpers
function isFunction(f) {
var getType = {};
return f && getType.toString.call(f) === '[object Function]';
}
module.exports = {
WithFragments: WithFragments,
Document: Document,
GroupDoc: GroupDoc
};
},{"./fragments":7,"./utils/date":13}],6:[function(require,module,exports){
"use strict";
/**
* A collection of experiments currently available
* @param data the json data received from the Prismic API
* @constructor
*/
function Experiments(data) {
var drafts = [];
var running = [];
if (data) {
data.drafts && data.drafts.forEach(function (exp) {
drafts.push(new Experiment(exp));
});
data.running && data.running.forEach(function (exp) {
running.push(new Experiment(exp));
});
}
this.drafts = drafts;
this.running = running;
}
Experiments.prototype.current = function () {
return this.running.length > 0 ? this.running[0] : null;
};
/**
* Get the current running experiment variation ref from a cookie content
*/
Experiments.prototype.refFromCookie = function (cookie) {
if (!cookie || cookie.trim() === "") return null;
var splitted = cookie.trim().split(" ");
if (splitted.length < 2) return null;
var expId = splitted[0];
var varIndex = parseInt(splitted[1], 10);
var exp = this.running.filter(function (exp) {
return exp.googleId() == expId && exp.variations.length > varIndex;
})[0];
return exp ? exp.variations[varIndex].ref() : null;
};
function Experiment(data) {
this.data = data;
var variations = [];
data.variations && data.variations.forEach(function (v) {
variations.push(new Variation(v));
});
this.variations = variations;
}
Experiment.prototype.id = function () {
return this.data.id;
};
Experiment.prototype.googleId = function () {
return this.data.googleId;
};
Experiment.prototype.name = function () {
return this.data.name;
};
functi