diff --git a/.eslintrc.json b/.eslintrc.json
index b78236f..92b8a4a 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -23,6 +23,12 @@
"semi": [
"error",
"always"
- ]
+ ],
+ "no-var": "error",
+ "no-multi-spaces": "error",
+ "space-in-parens": "error",
+ "no-multiple-empty-lines": "error",
+ "prefer-const": "error",
+ "no-use-before-define": "error"
}
}
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
index f787ab3..75daf6f 100755
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,186 +1,225 @@
'use strict';
-const xml = require('xml');
+const baseXml = require('xml');
-/**
- * Check first argument. If true - push last argument to second argument
- *
- * @param condition
- * @param array
- * @param data
- */
-function pushIfConditionTrue(condition, array, data) {
- if (condition) {
- array.push(data);
+class TR {
+ constructor(options, items) {
+ options = options || {};
+
+ this.title = options.title || '';
+ this.link = options.link || '';
+ this.description = options.description || '';
+ this.language = options.language || 'ru';
+ this.items = [];
+
+ if (Array.isArray(items) && items.length > 0) {
+ const self = this;
+ items.forEach(function (item) {
+ self.item(item);
+ });
+ }
}
-}
-/**
- * @param related
- * @param itemValues
- * @param relatedInfinity
- */
-function addRelated(related, itemValues, relatedInfinity) {
- let relatedResult = related.map(function (rel) {
- return {
- link: [{
- _attr: {
- 'url': rel.link,
- 'img': rel.image_url
+ xml() {
+ return baseXml(this.generateXML(this));
+ }
+
+ item(data) {
+ data = data || {};
+ this.items.push(
+ this.itemData(data)
+ );
+
+ return this;
+ }
+
+ /**
+ * Check first argument. If true - push last argument to second argument
+ *
+ * @param condition
+ * @param array
+ * @param data
+ */
+ pushIfConditionTrue(condition, array, data) {
+ if (condition) {
+ array.push(data);
+ }
+ }
+
+ /**
+ * @param related
+ * @param itemValues
+ * @param relatedInfinity
+ */
+ addRelated(related, itemValues, relatedInfinity) {
+ const relatedResult = related.map(function (rel) {
+ return {
+ link: [{
+ _attr: {
+ 'url': rel.link,
+ 'img': rel.image_url
+ }
+ }, rel.text]
+ };
+ });
+
+ if (relatedInfinity) {
+ relatedResult.push({
+ _attr: {'type': 'infinity'}
+ });
+ }
+
+ this.pushIfConditionTrue(related, itemValues, {'yandex:related': relatedResult});
+ }
+
+ /**
+ * @param item
+ * @param itemValues
+ */
+ pushGoals(item, itemValues) {
+ if (item.goals.length > 0) {
+ item.goals.forEach(goal => itemValues.push({
+ 'turbo:goal': {
+ _attr: {
+ type: goal.type || 'yandex',
+ 'turbo-goal-id': goal.id,
+ name: goal.name,
+ id: goal.counter_id,
+ }
}
- }, rel.text]
- };
- });
+ }));
+ }
+ }
- if (relatedInfinity) {
- relatedResult.push({
- _attr: {'type': 'infinity'}
+ getImageContent(item) {
+ if (item.image_url.length > 0) {
+ let imageCaption = '';
+
+ if (item.image_caption.length > 0) {
+ imageCaption = `${item.image_caption} `;
+ }
+
+ return ` ${imageCaption} `;
+ }
+ return '';
+ }
+
+ getSubheading(item) {
+ if (item.subheading.length > 0) {
+ return `
${item.subheading} `;
+ }
+
+ return '';
+ }
+
+ getLinksString(links) {
+ return links.map(function (item) {
+ return `${item.text} `;
+ }).join('');
+ }
+
+ getMenu(item) {
+ if (Array.isArray(item.menu) && item.menu.length > 0) {
+ return `${this.getLinksString(item.menu)} `;
+ }
+
+ return '';
+ }
+
+ getBreadcrumbs(item) {
+ if (Array.isArray(item.breadcrumbs) && item.breadcrumbs.length > 0) {
+ return `${this.getLinksString(item.breadcrumbs)}
`;
+ }
+
+ return '';
+ }
+
+ /**
+ * Items processing
+ * @param items
+ * @param channel
+ */
+ itemsProcessing(items, channel) {
+ const self = this;
+ items.forEach(function (item) {
+ const itemValues = [];
+ itemValues.push({_attr: {'turbo': item.turboEnabled ? 'true' : 'false'}});
+ itemValues.push({link: item.url});
+
+ self.pushIfConditionTrue(item.turboSource, itemValues, {'turbo:source': item.turboSource});
+ self.pushIfConditionTrue(item.turboTopic, itemValues, {'turbo:topic': item.turboTopic});
+ self.pushIfConditionTrue(item.date, itemValues, {pubDate: new Date(item.date).toUTCString()});
+ self.pushIfConditionTrue(item.author, itemValues, {author: item.author});
+
+ const fullContent = `${self.getImageContent(item)}${item.title} ${self.getSubheading(item)}${self.getMenu(item)}${self.getBreadcrumbs(item)} ${item.content}`;
+
+ self.pushGoals(item, itemValues);
+
+ itemValues.push({'turbo:content': {_cdata: fullContent}});
+
+ if (typeof item.related !== 'undefined') {
+ self.addRelated(item.related, itemValues, item.relatedInfinity);
+ }
+
+ channel.push({item: itemValues});
});
}
- pushIfConditionTrue(related, itemValues, {'yandex:related': relatedResult});
-}
+ /**
+ * @param options
+ *
+ * @returns {{rss: *[]}}
+ */
+ generateXML(options) {
-/**
- * @param item
- * @param itemValues
- */
-function pushGoals(item, itemValues) {
- if (item.goals.length > 0) {
- item.goals.forEach(goal => itemValues.push({
- 'turbo:goal': {
- _attr: {
- type: goal.type || 'yandex',
- 'turbo-goal-id': goal.id,
- name: goal.name,
- id: goal.counter_id,
- }
- }
- }));
+ const channel = [];
+
+ channel.push({title: {_cdata: options.title}});
+ channel.push({link: options.link});
+ channel.push({description: {_cdata: options.description}});
+ channel.push({language: options.language});
+
+ this.itemsProcessing(options.items, channel);
+
+ const _attr = {
+ 'xmlns:yandex': 'http://news.yandex.ru',
+ 'xmlns:media': 'http://search.yahoo.com/mrss/',
+ 'xmlns:turbo': 'http://turbo.yandex.ru',
+ version: '2.0'
+ };
+
+ return {
+ rss: [
+ {_attr: _attr},
+ {channel: channel}
+ ]
+ };
+ }
+
+ /**
+ * @param data
+ * @returns {*}
+ */
+ itemData(data) {
+ return {
+ title: data.title || '', // h1
+ subheading: data.subheading || '', // h2
+ image_url: data.image_url || '',
+ image_caption: data.image_caption || '',
+ url: data.url || data.link,
+ author: data.author,
+ date: data.date || data.pubDate,
+ content: data.content,
+ menu: data.menu || [],
+ breadcrumbs: data.breadcrumbs || [],
+ related: data.related,
+ relatedInfinity: data.relatedInfinity || false,
+ turboSource: data.turboSource || '',
+ turboTopic: data.turboTopic || '',
+ goals: data.goals || [],
+ turboEnabled: data.turboEnabled !== undefined ? data.turboEnabled : true,
+ };
}
}
-/**
- * Items processing
- * @param items
- * @param channel
- */
-function items(items, channel) {
- items.forEach(function (item) {
- let itemValues = [];
- itemValues.push({_attr: {'turbo': item.turboEnabled ? 'true' : 'false'}});
- itemValues.push({link: item.url});
-
- pushIfConditionTrue(item.turboSource, itemValues, {'turbo:source': item.turboSource});
- pushIfConditionTrue(item.turboTopic, itemValues, {'turbo:topic': item.turboTopic});
- pushIfConditionTrue(item.date, itemValues, {pubDate: new Date(item.date).toUTCString()});
- pushIfConditionTrue(item.author, itemValues, {author: item.author});
-
- let img = '', menu = '';
-
- if (item.image_url) {
- if (!item.image_caption) img = ' ';
- else img = '' + item.image_caption + ' ';
- }
-
- if (Array.isArray(item.menu)) {
- menu = '' + item.menu.map(function (item) {
- return '' + item.text + ' ';
- }).join('') + ' ';
- }
-
- let fullContent = '' + img + ' ' + item.title + ' ' + menu + ' ' + item.content;
-
- pushGoals(item, itemValues);
-
- itemValues.push({'turbo:content': {_cdata: fullContent}});
-
- if (typeof item.related !== 'undefined') {
- addRelated(item.related, itemValues, item.relatedfinity);
- }
-
- channel.push({item: itemValues});
- });
-}
-
-/**
- * @param data
- * @returns {{rss: *[]}}
- */
-function generateXML(data) {
-
- let channel = [];
-
- channel.push({title: {_cdata: data.title}});
- channel.push({link: data.link});
- channel.push({description: {_cdata: data.description || data.title}});
- channel.push({language: data.language});
-
- items(data.items, channel);
-
- let _attr = {
- 'xmlns:yandex': 'http://news.yandex.ru',
- 'xmlns:media': 'http://search.yahoo.com/mrss/',
- 'xmlns:turbo': 'http://turbo.yandex.ru',
- version: '2.0'
- };
-
- return {
- rss: [
- {_attr: _attr},
- {channel: channel}
- ]
- };
-}
-
-/**
- * @param data
- * @returns {*}
- */
-function itemData(data) {
- return {
- title: data.title || '',
- description: data.description || '',
- image_url: data.image_url,
- image_caption: data.image_caption,
- url: data.url || data.link,
- author: data.author,
- date: data.date || data.pubDate,
- content: data.content,
- menu: data.menu,
- related: data.related,
- relatedfinity: data.relatedfinity || false,
- turboSource: data.turboSource || '',
- turboTopic: data.turboTopic || '',
- goals: data.goals || [],
- turboEnabled: data.turboEnabled !== undefined ? data.turboEnabled: true,
- };
-}
-/**
- * Base function
- * @param options
- * @param items
- * @constructor
- */
-function TR(options, items) {
- options = options || {};
-
- this.title = options.title || '';
- this.link = options.link || '';
- this.description = options.description || '';
- this.language = options.language || 'ru';
-
- this.items = items || [];
-
- this.item = function (data) {
- data = data || {};
- this.items.push(itemData(data));
- return this;
- };
-
- this.xml = function () {
- return xml(generateXML(this));
- };
-}
-
module.exports = TR;
diff --git a/readme.md b/readme.md
index 5ce6104..6d9d197 100644
--- a/readme.md
+++ b/readme.md
@@ -35,24 +35,34 @@ feed.item(itemOptions);
##### itemOptions
* `title` **string** Заголовок страницы.
+ * `subheading` _optional_ **string** Подзаголовок страницы.
* `image_url` _optional_ **url string** Адрес изображения, которое используется в качестве обложки. Изображение может быть в любом формате.
+ * `image_caption` _optional_ **string** Подпись к изображению обложки.
* `link` **url string** URL страницы сайта, для которой нужно сформировать Турбо-страницу.
* `author` _optional_ **string** Автор статьи, размещенной на странице.
* `date` или `pubDate` **string** Время публикации контента на сайте источника. Передается в формате RFC-822.
* `content` **string** Содержимое страницы
* `menu` _optional_ **array** Внимание! Меню будет отображаться только в том случае, если в настройках на странице Яндекс Вебмастер -> Турбо-страницы -> Настройки, содержимое 'Меню Турбо-страниц' пустое!
+ * `breadcrumbs` _optional_ **array** Навигационная ссылка https://yandex.ru/dev/turbo/doc/rss/elements/header.html#breadcrumbs
* `related` _optional_ **array** Аффилированные ссылки `yandex:related` в конце статьи. Вы можете разместить ссылки на другие ресурсы или настроить отображение непрерывной ленты статей, реализованной, например с помощью AJAX.
- * `relatedfinity` _optional_ **bool** Непрерывная лента статей
+ * `relatednfinity` _optional_ **bool** Непрерывная лента статей (Параметр был переименован из relatedfinity)
* `turboSource` _optional_ **string** URL страницы-источника, который можно передать в Яндекс.Метрику.
* `turboTopic` _optional_ **string** Заголовок страницы, который можно передать в Яндекс.Метрику.
* `goals` _optional_ **array** массив типа: { _id_ - внутренний идентификатор цели (turbo-goal-id), _name_ - имя цели, _counter_id_ - id счётчика яндекс-метрики }
* `turboEnabled`_optional_ **bool** Принудительная установка атрибута "turbo". По умолчанию true. Установка в false позволит скрыть отображение турбо-страницы
+
###### menu array
menu должен содержать массив объектов со следующими опциями:
* `link` **url string** ссылка
* `text` **string** текст ссылки. не должен содержать html
-
+
+###### breadcrumbs array
+Один элемент хлебных крошек должен содержать массив объектов со следующими опциями:
+
+* `link` **url string** ссылка
+* `text` **string** текст ссылки (не должен содержать html)
+
###### related array
related должен содержать массив объектов со следующими опциями:
diff --git a/test/expectedOutput/breadcrumbs.xml b/test/expectedOutput/breadcrumbs.xml
new file mode 100644
index 0000000..0ab288c
--- /dev/null
+++ b/test/expectedOutput/breadcrumbs.xml
@@ -0,0 +1 @@
+ http://example.com/rss.xmlru https://example.com/soups/with-potatoes/Sat, 26 May 2018 21:00:00 GMT LightAir Суп с картофелем Для приготовления супа с картофелем, возьмите
]]>
diff --git a/test/expectedOutput/defaultItem.xml b/test/expectedOutput/defaultItem.xml
index 48321b5..afafc9c 100644
--- a/test/expectedOutput/defaultItem.xml
+++ b/test/expectedOutput/defaultItem.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru undefined]]>
+ http://example.com/rss.xmlru undefined]]>
diff --git a/test/expectedOutput/defaultItemTurboFalse.xml b/test/expectedOutput/defaultItemTurboFalse.xml
index aad75c3..e22633a 100644
--- a/test/expectedOutput/defaultItemTurboFalse.xml
+++ b/test/expectedOutput/defaultItemTurboFalse.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru undefined]]>
+ http://example.com/rss.xmlru undefined]]>
diff --git a/test/expectedOutput/defaultOneItem.xml b/test/expectedOutput/defaultOneItem.xml
index e6c9d6b..b0c91da 100644
--- a/test/expectedOutput/defaultOneItem.xml
+++ b/test/expectedOutput/defaultOneItem.xml
@@ -1 +1 @@
-ru undefined]]>
+ru undefined]]>
diff --git a/test/expectedOutput/defaultTwoItem.xml b/test/expectedOutput/defaultTwoItem.xml
new file mode 100644
index 0000000..85c94b1
--- /dev/null
+++ b/test/expectedOutput/defaultTwoItem.xml
@@ -0,0 +1 @@
+ru undefined]]> undefined]]>
diff --git a/test/expectedOutput/goal.xml b/test/expectedOutput/goal.xml
index fe5a1c1..3ae1ba9 100644
--- a/test/expectedOutput/goal.xml
+++ b/test/expectedOutput/goal.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT vvm.space item title hello
]]> related link text 1 related link text 2
+ http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT vvm.space item title hello
]]> related link text 1 related link text 2
diff --git a/test/expectedOutput/imageCaption.xml b/test/expectedOutput/imageCaption.xml
new file mode 100644
index 0000000..91c8416
--- /dev/null
+++ b/test/expectedOutput/imageCaption.xml
@@ -0,0 +1 @@
+ http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir this is image caption item title hello
]]> related link text 1 related link text 2
diff --git a/test/expectedOutput/menu.xml b/test/expectedOutput/menu.xml
index afba1b6..7be1a8f 100644
--- a/test/expectedOutput/menu.xml
+++ b/test/expectedOutput/menu.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title Главная О сайте hello
]]> related link text 1 related link text 2 undefined]]>
+ http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title Главная О сайте hello
]]> related link text 1 related link text 2 undefined]]>
diff --git a/test/expectedOutput/relatedItem.xml b/test/expectedOutput/relatedItem.xml
index 7d386f7..47a6618 100644
--- a/test/expectedOutput/relatedItem.xml
+++ b/test/expectedOutput/relatedItem.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title hello
]]> related link text 1 related link text 2
+ http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title hello
]]> related link text 1 related link text 2
diff --git a/test/expectedOutput/relatedItemInfinity.xml b/test/expectedOutput/relatedItemInfinity.xml
index bd80cb4..c271dec 100644
--- a/test/expectedOutput/relatedItemInfinity.xml
+++ b/test/expectedOutput/relatedItemInfinity.xml
@@ -1 +1 @@
- http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title hello
]]> related link text 1 related link text 2
+ http://example.com/rss.xmlru http://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMT LightAir item title hello
]]> related link text 1 related link text 2
diff --git a/test/expectedOutput/subheading.xml b/test/expectedOutput/subheading.xml
new file mode 100644
index 0000000..490dec1
--- /dev/null
+++ b/test/expectedOutput/subheading.xml
@@ -0,0 +1 @@
+ http://example.com/rss.xmlru item title Subheading undefined]]>
diff --git a/test/index.js b/test/index.js
index e6c0860..54f4134 100644
--- a/test/index.js
+++ b/test/index.js
@@ -24,16 +24,33 @@ const relatedOptions = [{
require('mockdate').set('Wed, 10 Dec 2014 19:04:57 GMT');
test('empty feed', function (t) {
- t.plan(2);
- let feed = new TR();
+ t.plan(3);
+
+ const feed = new TR();
t.equal(feed.xml(), expectedOutput.default.trim());
+
feed.item();
t.equal(feed.xml(), expectedOutput.defaultOneItem.trim());
+
+ feed.item();
+ t.equal(feed.xml(), expectedOutput.defaultTwoItem.trim());
+});
+
+
+test('empty feed constructor', function (t) {
+ t.plan(1);
+
+ const feed = new TR(
+ {},
+ [{}, {}]
+ );
+
+ t.equal(feed.xml(), expectedOutput.defaultTwoItem.trim());
});
test('default item', function (t) {
t.plan(1);
- let feed = new TR(baseOptions);
+ const feed = new TR(baseOptions);
feed.item({});
@@ -42,7 +59,7 @@ test('default item', function (t) {
test('related item', function (t) {
t.plan(1);
- let feed = new TR(baseOptions);
+ const feed = new TR(baseOptions);
feed.item({
title: 'item title',
@@ -60,7 +77,7 @@ test('related item', function (t) {
test('related item', function (t) {
t.plan(1);
- let feed = new TR(baseOptions);
+ const feed = new TR(baseOptions);
feed.item({
title: 'item title',
@@ -69,7 +86,7 @@ test('related item', function (t) {
author: 'LightAir',
date: 'May 27, 2018 00:00 AM',
content: 'hello
',
- relatedfinity: true,
+ relatedInfinity: true,
related: relatedOptions
});
@@ -78,7 +95,7 @@ test('related item', function (t) {
test('menu', function (t) {
t.plan(1);
- let feed = new TR(baseOptions);
+ const feed = new TR(baseOptions);
feed.item({
title: 'item title',
@@ -87,7 +104,7 @@ test('menu', function (t) {
author: 'LightAir',
date: 'May 27, 2018 00:00 AM',
content: 'hello
',
- relatedfinity: true,
+ relatedInfinity: true,
menu: [{
link: 'http://example.com/',
text: 'Главная',
@@ -103,9 +120,9 @@ test('menu', function (t) {
t.equal(feed.xml(), expectedOutput.menu.trim());
});
-test('goals', function (t) {
+test('goals', function (t) {
t.plan(1);
- let feed = new TR(baseOptions);
+ const feed = new TR(baseOptions);
feed.item({
title: 'item title',
@@ -126,3 +143,60 @@ test('goals', function (t) {
t.equal(feed.xml(), expectedOutput.goal.trim());
});
+
+test('image caption', function (t) {
+ t.plan(1);
+ const feed = new TR(baseOptions);
+
+ feed.item({
+ title: 'item title',
+ image_url: 'http://example.com/example.png',
+ image_caption: 'this is image caption',
+ url: 'http://example.com/article4?this&that',
+ author: 'LightAir',
+ date: 'May 27, 2018 00:00 AM',
+ menu: 'Текст ссылки Текст ссылки ',
+ content: 'hello
',
+ related: relatedOptions
+ });
+
+ t.equal(feed.xml(), expectedOutput.imageCaption.trim());
+});
+
+test('subheading', function (t) {
+ t.plan(1);
+ const feed = new TR(baseOptions);
+
+ feed.item({
+ title: 'item title',
+ subheading: 'Subheading',
+ });
+
+ t.equal(feed.xml(), expectedOutput.subheading.trim());
+});
+
+test('breadcrumbs', function (t) {
+ t.plan(1);
+ const feed = new TR(baseOptions);
+
+ feed.item({
+ title: 'Суп с картофелем',
+ image_url: 'https://example.com/example.png',
+ breadcrumbs: [{
+ link: 'https://example.com/',
+ text: 'Главная',
+ }, {
+ link: 'https://example.com/soups/',
+ text: 'Супы',
+ }, {
+ link: 'https://example.com/soups/with-potatoes/',
+ text: 'Суп с картофелем'
+ }],
+ url: 'https://example.com/soups/with-potatoes/',
+ author: 'LightAir',
+ date: 'May 27, 2018 00:00 AM',
+ content: 'Для приготовления супа с картофелем, возьмите
'
+ });
+
+ t.equal(feed.xml(), expectedOutput.breadcrumbs.trim());
+});
\ No newline at end of file