pageflow 12.1.0 → 12.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pageflow might be problematic. Click here for more details.

Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -171
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +22 -2
  5. data/admins/pageflow/entry.rb +3 -1
  6. data/admins/pageflow/user.rb +9 -3
  7. data/app/assets/javascripts/pageflow/browser/agent.js +20 -9
  8. data/app/assets/javascripts/pageflow/browser/autoplay_support.js +3 -2
  9. data/app/assets/javascripts/pageflow/dist/react.js +6000 -4941
  10. data/app/assets/javascripts/pageflow/editor/initializers/setup_collections.js +0 -1
  11. data/app/assets/javascripts/pageflow/editor/views/page_preview_view.js +0 -5
  12. data/app/assets/javascripts/pageflow/links.js +0 -7
  13. data/app/assets/javascripts/pageflow/ready.js +2 -0
  14. data/app/assets/javascripts/pageflow/slideshow.js +1 -0
  15. data/app/assets/javascripts/pageflow/slideshow/hide_text_on_swipe.js +10 -5
  16. data/app/assets/javascripts/pageflow/slideshow/page_split_layout.js +74 -0
  17. data/app/assets/javascripts/pageflow/slideshow/page_widget.js +25 -1
  18. data/app/assets/javascripts/pageflow/slideshow/swipe_gesture.js +5 -3
  19. data/app/assets/javascripts/pageflow/ui.js +1 -0
  20. data/app/assets/javascripts/pageflow/ui/templates/inputs/text_area_input.jst.ejs +1 -1
  21. data/app/assets/javascripts/pageflow/ui/templates/inputs/text_input.jst.ejs +1 -1
  22. data/app/assets/javascripts/pageflow/ui/views/inputs/color_input_view.js +105 -0
  23. data/app/assets/javascripts/pageflow/visited.js +62 -34
  24. data/app/assets/javascripts/pageflow/widgets/overview.js +18 -5
  25. data/app/assets/stylesheets/pageflow/base.scss +0 -1
  26. data/app/assets/stylesheets/pageflow/entries.scss +11 -3
  27. data/app/assets/stylesheets/pageflow/mixins.scss +1 -0
  28. data/app/assets/stylesheets/pageflow/mixins/layout_direction.scss +72 -0
  29. data/app/assets/stylesheets/pageflow/mixins/shadow.scss +20 -0
  30. data/app/assets/stylesheets/pageflow/navigation_bar.scss +8 -2
  31. data/app/assets/stylesheets/pageflow/navigation_mobile.scss +1 -2
  32. data/app/assets/stylesheets/pageflow/overview.scss +10 -11
  33. data/app/assets/stylesheets/pageflow/page.scss +3 -37
  34. data/app/assets/stylesheets/pageflow/page_transitions/fade.scss +7 -3
  35. data/app/assets/stylesheets/pageflow/page_transitions/fade_to_black.scss +1 -10
  36. data/app/assets/stylesheets/pageflow/slideshow.scss +1 -2
  37. data/app/assets/stylesheets/pageflow/themes/default/anchors.scss +5 -0
  38. data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -0
  39. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner.scss +9 -3
  40. data/app/assets/stylesheets/pageflow/themes/default/logo.scss +9 -3
  41. data/app/assets/stylesheets/pageflow/themes/default/logo/alignment.scss +2 -2
  42. data/app/assets/stylesheets/pageflow/themes/default/logo/variant/background_image.scss +52 -5
  43. data/app/assets/stylesheets/pageflow/themes/default/logo/variant/watermark.scss +6 -3
  44. data/app/assets/stylesheets/pageflow/{multimedia_alert.scss → themes/default/multimedia_alert.scss} +32 -11
  45. data/app/assets/stylesheets/pageflow/themes/default/overview.scss +7 -3
  46. data/app/assets/stylesheets/pageflow/themes/default/overview/icons/icon_font.scss +20 -4
  47. data/app/assets/stylesheets/pageflow/themes/default/page.scss +20 -53
  48. data/app/assets/stylesheets/pageflow/themes/default/page/line_lengths.scss +3 -8
  49. data/app/assets/stylesheets/pageflow/themes/default/page/paddings.scss +72 -0
  50. data/app/assets/stylesheets/pageflow/themes/default/page/scroller.scss +3 -0
  51. data/app/assets/stylesheets/pageflow/themes/default/page/shadow.scss +27 -0
  52. data/app/assets/stylesheets/pageflow/themes/default/player_controls/classic/control_bar.scss +1 -0
  53. data/app/assets/stylesheets/pageflow/themes/default/player_controls/slim/container.scss +1 -1
  54. data/app/assets/stylesheets/pageflow/ui.scss +2 -0
  55. data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +25 -0
  56. data/app/controllers/pageflow/application_controller.rb +17 -3
  57. data/app/helpers/pageflow/overview_helper.rb +11 -0
  58. data/app/helpers/pageflow/text_direction_helper.rb +7 -0
  59. data/app/policies/pageflow/user_policy.rb +20 -0
  60. data/app/views/admin/accounts/_account_details.html.arb +5 -3
  61. data/app/views/admin/accounts/_theming_details.html.arb +5 -3
  62. data/app/views/admin/entries/_attributes_table.html.arb +3 -1
  63. data/app/views/components/pageflow/admin/extensible_attributes_table.rb +88 -0
  64. data/app/views/layouts/pageflow/application.html.erb +1 -1
  65. data/app/views/pageflow/entries/edit.html.erb +1 -1
  66. data/app/views/pageflow/entries/overview/_page.html.erb +3 -1
  67. data/app/views/pageflow/entries/show.html.erb +2 -1
  68. data/config/initializers/admin_attributes_table_rows.rb +3 -0
  69. data/config/locales/de.yml +1 -1
  70. data/lib/pageflow.rb +4 -2
  71. data/lib/pageflow/ability_mixin.rb +8 -0
  72. data/lib/pageflow/admin/attributes_table_rows.rb +47 -0
  73. data/lib/pageflow/configuration.rb +27 -3
  74. data/lib/pageflow/engine.rb +1 -0
  75. data/lib/pageflow/news_item_api.rb +18 -0
  76. data/lib/pageflow/version.rb +1 -1
  77. data/spec/factories/accounts.rb +7 -2
  78. data/spec/factories/entries.rb +7 -2
  79. data/vendor/assets/javascripts/iscroll.js +49 -34
  80. metadata +53 -12
@@ -33,7 +33,6 @@ pageflow.app.addInitializer(function(options) {
33
33
 
34
34
  pageflow.pages.sort();
35
35
 
36
- // TODO
37
36
  pageflow.storylines.on('sort', _.debounce(function() {
38
37
  pageflow.storylines.saveOrder();
39
38
  }, 100));
@@ -67,7 +67,6 @@ pageflow.PagePreviewView = Backbone.Marionette.View.extend({
67
67
  pageflow.events.trigger('page:update', this.model);
68
68
 
69
69
  this.refreshScroller();
70
- this.ensureTargetBlankForContentLinks();
71
70
  this.updateChapterBeginningClass();
72
71
  },
73
72
 
@@ -88,10 +87,6 @@ pageflow.PagePreviewView = Backbone.Marionette.View.extend({
88
87
  this.$el.page('refreshScroller');
89
88
  },
90
89
 
91
- ensureTargetBlankForContentLinks: function() {
92
- pageflow.links.ensureTargetBlankForContentLinks(this.el);
93
- },
94
-
95
90
  initEmbeddedViews: function() {
96
91
  var view = this;
97
92
 
@@ -2,7 +2,6 @@ pageflow.links = {
2
2
  setup: function() {
3
3
  this.ensureClickOnEnterKeyPress();
4
4
  this.setupContentSkipLinks();
5
- this.ensureTargetBlankForContentLinks();
6
5
  },
7
6
 
8
7
  ensureClickOnEnterKeyPress: function() {
@@ -25,11 +24,5 @@ pageflow.links = {
25
24
  e.preventDefault();
26
25
  return false;
27
26
  });
28
- },
29
-
30
- // There was a time when the rich text editor did not add target
31
- // attributes to inline links even though it should have.
32
- ensureTargetBlankForContentLinks: function(context) {
33
- $('.contentText p a', context).attr('target', '_blank');
34
27
  }
35
28
  };
@@ -8,6 +8,8 @@ pageflow.ready = new $.Deferred(function(readyDeferred) {
8
8
  readyDeferred.resolve();
9
9
  });
10
10
 
11
+ pageflow.Visited.setup();
12
+
11
13
  slideshow.each(function() {
12
14
  pageflow.events.trigger('seed:loaded');
13
15
 
@@ -1,5 +1,6 @@
1
1
  //=require ./slideshow/atmo
2
2
  //=require ./slideshow/lazy_page_widget
3
+ //=require ./slideshow/page_split_layout
3
4
  //=require ./slideshow/page_widget
4
5
  //=require ./slideshow/scroller_widget
5
6
  //=require ./slideshow/scroll_indicator
@@ -2,18 +2,23 @@
2
2
  $.widget('pageflow.hideTextOnSwipe', {
3
3
  _create: function() {
4
4
  this.element.swipeGesture({
5
- orientation: 'x'
5
+ orientation: 'x',
6
+ eventTargetSelector: this.options.eventTargetSelector
6
7
  });
7
8
 
8
9
  this.element.on('swipegestureleft', function() {
9
10
  pageflow.hideText.activate();
10
11
  });
11
12
 
12
- this.element.on('touchstart MSPointerDown pointerdown mousedown', function() {
13
- if (pageflow.hideText.isActive()) {
14
- pageflow.hideText.deactivate();
13
+ this.element.on(
14
+ 'touchstart MSPointerDown pointerdown mousedown',
15
+ this.options.eventTargetSelector,
16
+ function() {
17
+ if (pageflow.hideText.isActive()) {
18
+ pageflow.hideText.deactivate();
19
+ }
15
20
  }
16
- });
21
+ );
17
22
 
18
23
  this.element.on('scrollermove', function() {
19
24
  if (pageflow.hideText.isActive()) {
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Utility functions for page types that dynamically switch to a two
3
+ * column layout where some kind of embed is displayed next to the
4
+ * text (i.e. `pageflow-chart` and `pageflow-embedded-video`).
5
+ *
6
+ * Works closely with the `page-with_split_layout` CSS class (see
7
+ * `pageflow/themes/default/page/line_lengths.scss`).
8
+ *
9
+ * @since 12.2
10
+ */
11
+ pageflow.pageSplitLayout = (function() {
12
+ return {
13
+ /**
14
+ * Determine if the page is wide enough to display two columns.
15
+ *
16
+ * @memberof pageflow.pageSplitLayout
17
+ */
18
+ pageIsWideEnough: function(pageElement) {
19
+ var pageClientRect = pageElement[0].getBoundingClientRect();
20
+ var contentClientRect = getContentClientRect(pageElement, pageClientRect);
21
+
22
+ var spaceRightFromTitle = pageClientRect.right - contentClientRect.right;
23
+ var spaceLeftFromTitle = contentClientRect.left - pageClientRect.left;
24
+
25
+ var leftPositionedEmbedWidth = pageClientRect.width * 0.51;
26
+ var rightPositionedEmbedWidth = pageClientRect.width * 0.55;
27
+
28
+ return (spaceLeftFromTitle >= leftPositionedEmbedWidth ||
29
+ spaceRightFromTitle >= rightPositionedEmbedWidth);
30
+ }
31
+ };
32
+
33
+ function getContentClientRect(pageElement, pageClientRect) {
34
+ var pageTitle = pageElement.find('.page_header .title');
35
+ var contentText = pageElement.find('.contentText p');
36
+
37
+ var pageTitleClientRect = pageTitle[0].getBoundingClientRect();
38
+ var contentTextClientRect = contentText[0].getBoundingClientRect();
39
+
40
+ var contentRight;
41
+ var contentLeft;
42
+
43
+ if (isTitleHidden(pageTitleClientRect)) {
44
+ contentRight = contentTextClientRect.right;
45
+ contentLeft = contentTextClientRect.left;
46
+ }
47
+ else {
48
+ contentRight = Math.max(pageTitleClientRect.right, contentTextClientRect.right);
49
+ contentLeft = pageTitleClientRect.left;
50
+ }
51
+
52
+ var contentTranslation = getContentTranslationCausedByHiddenText(pageElement,
53
+ pageClientRect);
54
+
55
+ return {
56
+ right: contentRight - contentTranslation,
57
+ left: contentLeft - contentTranslation
58
+ };
59
+ }
60
+
61
+ function isTitleHidden(pageTitleClientRect) {
62
+ return pageTitleClientRect.width === 0;
63
+ }
64
+
65
+ function getContentTranslationCausedByHiddenText(pageElement, pageClientRect) {
66
+ var contentWrapper = pageElement.find('.contentWrapper');
67
+ var contentWrapperClientRect = contentWrapper[0].getBoundingClientRect();
68
+
69
+ var contentWrapperMarginInsidePage = contentWrapper[0].offsetLeft;
70
+ var nonTranslatedContentWrapperLeft = pageClientRect.left + contentWrapperMarginInsidePage;
71
+
72
+ return contentWrapperClientRect.left - nonTranslatedContentWrapperLeft;
73
+ }
74
+ }());
@@ -6,6 +6,7 @@
6
6
  this.index = this.options.index;
7
7
 
8
8
  this._setupNearBoundaryCssClasses();
9
+ this._setupContentLinkTargetHandling();
9
10
 
10
11
  this.reinit();
11
12
  },
@@ -185,7 +186,9 @@
185
186
  _setupHideTextOnSwipe: function() {
186
187
  if (pageflow.entryData.getThemingOption('hide_text_on_swipe') &&
187
188
  !this.pageType.noHideTextOnSwipe) {
188
- this.content.hideTextOnSwipe();
189
+ this.element.hideTextOnSwipe({
190
+ eventTargetSelector: '.content > .scroller'
191
+ });
189
192
  }
190
193
  },
191
194
 
@@ -201,6 +204,27 @@
201
204
  element.removeClass('is_near_' + boundary);
202
205
  });
203
206
  });
207
+ },
208
+
209
+ _setupContentLinkTargetHandling: function() {
210
+ this._on({
211
+ 'click .contentText p a': function(event) {
212
+ var href = $(event.currentTarget).attr('href');
213
+
214
+ if (href[0] === '#') {
215
+ pageflow.slides.goToByPermaId(href.substr(1));
216
+ }
217
+ else {
218
+ // There was a time when the rich text editor did not add
219
+ // target attributes to inline links even though it should
220
+ // have. Ensure all content links to external urls open in
221
+ // new tab.
222
+ window.open(href, '_blank');
223
+ }
224
+
225
+ event.preventDefault();
226
+ }
227
+ });
204
228
  }
205
229
  });
206
230
  }(jQuery));
@@ -11,7 +11,9 @@
11
11
  maxDuration: 500
12
12
  }, this.options);
13
13
 
14
- this.element.on('touchstart MSPointerDown pointerdown', _.bind(function(event) {
14
+ var selector = this.options.eventTargetSelector;
15
+
16
+ this.element.on('touchstart MSPointerDown pointerdown', selector, _.bind(function(event) {
15
17
  if (isNonTouchPointer(event)) { return; }
16
18
  var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent;
17
19
 
@@ -24,7 +26,7 @@
24
26
  startTime = new Date().getTime();
25
27
  }, this));
26
28
 
27
- this.element.on('touchmove MSPointerMove pointermove', _.bind(function(event) {
29
+ this.element.on('touchmove MSPointerMove pointermove', selector, _.bind(function(event) {
28
30
  if (isNonTouchPointer(event)) { return; }
29
31
  var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent;
30
32
 
@@ -32,7 +34,7 @@
32
34
  distY = point.pageY - startY;
33
35
  }, this));
34
36
 
35
- this.element.on('touchend MSPointerUp pointerup', _.bind(function(event) {
37
+ this.element.on('touchend MSPointerUp pointerup', selector, _.bind(function(event) {
36
38
  if (isNonTouchPointer(event)) { return; }
37
39
  var elapsedTime = new Date().getTime() - startTime;
38
40
 
@@ -1,6 +1,7 @@
1
1
  //= require_self
2
2
 
3
3
  //= require wysihtml5x-toolbar
4
+ //= require jquery.minicolors
4
5
 
5
6
  //= require i18n
6
7
  //= require i18n/translations
@@ -4,7 +4,7 @@
4
4
  </label>
5
5
 
6
6
  <!-- inline style for wysihtml5 to pick up -->
7
- <textarea style="width: 100%;"></textarea>
7
+ <textarea style="width: 100%;" dir="auto"></textarea>
8
8
 
9
9
  <div class="toolbar">
10
10
  <a data-wysihtml5-command="bold" title="<%= I18n.t('pageflow.ui.templates.inputs.text_area_input.bold') %>"></a>
@@ -2,4 +2,4 @@
2
2
  <span class="name"></span>
3
3
  <span class="inline_help"></span>
4
4
  </label>
5
- <input type="text" />
5
+ <input type="text" dir="auto" />
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Input view for a color value in hex representation.
3
+ *
4
+ * @param {string|function} [options.defaultValue]
5
+ * Color value to display by default. The corresponding value is not
6
+ * stored in the model. Selecting the default value when a different
7
+ * value was set before, unsets the attribute in the model.
8
+ *
9
+ * @param {string} [options.defaultValueBinding]
10
+ * Name of an attribute the default value depends on. If a function
11
+ * is used as defaultValue option, it will be passed the value of the
12
+ * defaultValueBinding attribute each time it changes. If no
13
+ * defaultValue option is set, the value of the defaultValueBinding
14
+ * attribute will be used as default value.
15
+ *
16
+ * @param {string[]} [options.swatches]
17
+ * Preset color values to be displayed inside the picker drop
18
+ * down. The default value, if present, is always used as the
19
+ * first swatch automatically.
20
+ *
21
+ * @see {@link module:pageflow/ui.pageflow.inputView pageflow.inputView} for further options
22
+ * @class
23
+ * @memberof module:pageflow/ui
24
+ */
25
+ pageflow.ColorInputView = Backbone.Marionette.ItemView.extend({
26
+ mixins: [pageflow.inputView],
27
+
28
+ template: 'pageflow/ui/templates/inputs/text_input',
29
+ className: 'color_input',
30
+
31
+ ui: {
32
+ input: 'input'
33
+ },
34
+
35
+ events: {
36
+ 'mousedown': 'refreshPicker'
37
+ },
38
+
39
+ onRender: function() {
40
+ this.ui.input.minicolors({
41
+ changeDelay: 200,
42
+ change: _.bind(function(color) {
43
+ if (color === this.defaultValue()) {
44
+ this.model.unset(this.options.propertyName);
45
+ }
46
+ else {
47
+ this.model.set(this.options.propertyName, color);
48
+ }
49
+ }, this)
50
+ });
51
+
52
+ this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
53
+
54
+ if (this.options.defaultValueBinding) {
55
+ this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings);
56
+ }
57
+
58
+ this.updateSettings();
59
+ },
60
+
61
+ updateSettings: function() {
62
+ this.ui.input.minicolors('settings', {
63
+ defaultValue: this.defaultValue(),
64
+ swatches: this.getSwatches()
65
+ });
66
+
67
+ this.load();
68
+ },
69
+
70
+ load: function() {
71
+ this.ui.input.minicolors('value',
72
+ this.model.get(this.options.propertyName) || this.defaultValue());
73
+ this.$el.toggleClass('is_default', !this.model.has(this.options.propertyName));
74
+ },
75
+
76
+ refreshPicker: function() {
77
+ this.ui.input.minicolors('value', {});
78
+ },
79
+
80
+ getSwatches: function() {
81
+ return _.chain([this.defaultValue(), this.options.swatches])
82
+ .flatten()
83
+ .uniq()
84
+ .compact()
85
+ .value();
86
+ },
87
+
88
+ defaultValue: function () {
89
+ var bindingValue;
90
+
91
+ if (this.options.defaultValueBinding) {
92
+ bindingValue = this.model.get(this.options.defaultValueBinding);
93
+ }
94
+
95
+ if (typeof this.options.defaultValue === 'function') {
96
+ return this.options.defaultValue(bindingValue);
97
+ }
98
+ else if ('defaultValue' in this.options) {
99
+ return this.options.defaultValue;
100
+ }
101
+ else {
102
+ return bindingValue;
103
+ }
104
+ }
105
+ });
@@ -1,35 +1,55 @@
1
- pageflow.visited = (function() {
1
+ pageflow.Visited = function(entryId, pages, events, cookies) {
2
+ var cookieName = '_pageflow_visited';
2
3
 
3
- var name, cookies = pageflow.cookies;
4
-
5
- $(function() {
6
- name = '_pageflow_' + pageflow.entryId + '_visited';
7
-
8
- if (pageflow.visited.enabled) {
9
- init();
10
- }
11
- });
4
+ var unvisitedPages = [];
12
5
 
13
6
  function init() {
14
- if (!cookies.hasItem(name)) {
15
- cookies.setItem(name, _getAllIds(), Infinity);
7
+ if (!cookies.hasItem(cookieName)) {
8
+ storeVisitedPageIds(getAllIds());
9
+ }
10
+ else {
11
+ var visitedIds = getVisitedPageIds();
12
+ unvisitedPages = _.difference(getAllIds(), visitedIds);
16
13
  }
17
14
 
18
- pageflow.ready.then(function() {
19
- pageflow.slides.on('pageactivate', function (e) {
20
- var id = e.target.getAttribute('id');
21
- var ids = _getCookieIds();
15
+ events.on('page:change', function (page) {
16
+ var id = page.getPermaId();
17
+ var ids = getVisitedPageIds();
22
18
 
23
- if (ids.indexOf(id) < 0) {
24
- ids.push(id);
25
- }
19
+ if (ids.indexOf(id) < 0) {
20
+ ids.push(id);
21
+ }
26
22
 
27
- cookies.setItem(name, ids, Infinity);
28
- });
23
+ storeVisitedPageIds(ids);
24
+ });
25
+ }
26
+
27
+ function migrateLegacyCookie() {
28
+ var legacyCookieName = '_pageflow_' + entryId + '_visited';
29
+
30
+ if (cookies.hasItem(legacyCookieName)) {
31
+ var ids = getCookieIds(legacyCookieName);
32
+ storeVisitedPageIds(_.uniq(ids));
33
+
34
+ cookies.removeItem(legacyCookieName);
35
+ }
36
+ }
37
+
38
+ function getAllIds() {
39
+ return pages.map(function(page) {
40
+ return page.perma_id;
29
41
  });
30
42
  }
31
43
 
32
- function _getCookieIds() {
44
+ function storeVisitedPageIds(ids) {
45
+ cookies.setItem(cookieName, ids, Infinity, location.pathname);
46
+ }
47
+
48
+ function getVisitedPageIds() {
49
+ return getCookieIds(cookieName);
50
+ }
51
+
52
+ function getCookieIds(name) {
33
53
  if (cookies.hasItem(name) && !!cookies.getItem(name)) {
34
54
  return cookies.getItem(name).split(',').map(function(id) {
35
55
  return parseInt(id, 10);
@@ -38,19 +58,27 @@ pageflow.visited = (function() {
38
58
  return [];
39
59
  }
40
60
 
41
- function _getAllIds() {
42
- return pageflow.pages.map(function(page) {
43
- return page.perma_id;
44
- });
45
- }
46
-
47
61
  return {
62
+ init: function() {
63
+ migrateLegacyCookie();
64
+ init();
65
+ },
66
+
48
67
  getUnvisitedPages: function() {
49
- if (pageflow.visited.enabled) {
50
- var visitedIds = _getCookieIds();
51
- return visitedIds.length ? _.difference(_getAllIds(), visitedIds) : visitedIds;
52
- }
53
- return [];
68
+ return unvisitedPages;
54
69
  }
55
70
  };
56
- }());
71
+ };
72
+
73
+ pageflow.Visited.setup = function() {
74
+ pageflow.visited = new pageflow.Visited(
75
+ pageflow.entryId,
76
+ pageflow.pages,
77
+ pageflow.events,
78
+ pageflow.cookies
79
+ );
80
+
81
+ if (pageflow.Visited.enabled) {
82
+ pageflow.visited.init();
83
+ }
84
+ };