From 01aa654b030c25b88d746ee236f7f92851534365 Mon Sep 17 00:00:00 2001 From: LightAir Date: Sun, 29 Aug 2021 02:19:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3.=20=D0=94=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BE=D0=BF=D1=86=D0=B8=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 8 +- lib/index.js | 381 ++++++++++-------- readme.md | 14 +- test/expectedOutput/breadcrumbs.xml | 1 + test/expectedOutput/defaultItem.xml | 2 +- test/expectedOutput/defaultItemTurboFalse.xml | 2 +- test/expectedOutput/defaultOneItem.xml | 2 +- test/expectedOutput/defaultTwoItem.xml | 1 + test/expectedOutput/goal.xml | 2 +- test/expectedOutput/imageCaption.xml | 1 + test/expectedOutput/menu.xml | 2 +- test/expectedOutput/relatedItem.xml | 2 +- test/expectedOutput/relatedItemInfinity.xml | 2 +- test/expectedOutput/subheading.xml | 1 + test/index.js | 94 ++++- 15 files changed, 324 insertions(+), 191 deletions(-) create mode 100644 test/expectedOutput/breadcrumbs.xml create mode 100644 test/expectedOutput/defaultTwoItem.xml create mode 100644 test/expectedOutput/imageCaption.xml create mode 100644 test/expectedOutput/subheading.xml 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 @@ +<![CDATA[title]]>http://example.com/rss.xmlruhttps://example.com/soups/with-potatoes/Sat, 26 May 2018 21:00:00 GMTLightAir

Суп с картофелем

Для приготовления супа с картофелем, возьмите

]]>
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 @@ -<![CDATA[title]]>http://example.com/rss.xmlru

undefined]]>
+<![CDATA[title]]>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 @@ -<![CDATA[title]]>http://example.com/rss.xmlru

undefined]]>
+<![CDATA[title]]>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 @@ -<link></link><description/><language>ru</language><item turbo="true"><link></link><turbo:content><![CDATA[<header> <h1></h1></header>undefined]]></turbo:content></item></channel></rss> +<rss xmlns:yandex="http://news.yandex.ru" xmlns:media="http://search.yahoo.com/mrss/" xmlns:turbo="http://turbo.yandex.ru" version="2.0"><channel><title/><link></link><description/><language>ru</language><item turbo="true"><link></link><turbo:content><![CDATA[<header><h1></h1></header>undefined]]></turbo:content></item></channel></rss> 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 @@ +<rss xmlns:yandex="http://news.yandex.ru" xmlns:media="http://search.yahoo.com/mrss/" xmlns:turbo="http://turbo.yandex.ru" version="2.0"><channel><title/><link></link><description/><language>ru</language><item turbo="true"><link></link><turbo:content><![CDATA[<header><h1></h1></header>undefined]]></turbo:content></item><item turbo="true"><link></link><turbo:content><![CDATA[<header><h1></h1></header>undefined]]></turbo:content></item></channel></rss> 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 @@ -<rss xmlns:yandex="http://news.yandex.ru" xmlns:media="http://search.yahoo.com/mrss/" xmlns:turbo="http://turbo.yandex.ru" version="2.0"><channel><title><![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTvvm.space

item title

hello

]]>
related link text 1related link text 2
+<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTvvm.space

item title

hello

]]>
related link text 1related 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 @@ +<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir
this is image caption

item title

hello

]]>
related link text 1related 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 @@ -<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

ГлавнаяО сайте

hello

]]>
related link text 1related link text 2

undefined]]>
+<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

ГлавнаяО сайте

hello

]]>
related link text 1related 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 @@ -<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

hello

]]>
related link text 1related link text 2
+<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

hello

]]>
related link text 1related 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 @@ -<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

hello

]]>
related link text 1related link text 2
+<![CDATA[title]]>http://example.com/rss.xmlruhttp://example.com/article4?this&thatSat, 26 May 2018 21:00:00 GMTLightAir

item title

hello

]]>
related link text 1related 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 @@ +<![CDATA[title]]>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