"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(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(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(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(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() {
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() {
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() {
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(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(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(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(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(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(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(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(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(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(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(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(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(name, linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function(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(linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function(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(linkResolver) {
if (!isFunction(linkResolver)) {
// Backward compatibility with the old ctx argument
var ctx = linkResolver;
linkResolver = function(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() {
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(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
};