Skip to content

Commit 853c3e0

Browse files
authored
Merge pull request #2115 from tf/cutoff
Cutoff modes
2 parents c2a7d3d + 18aa818 commit 853c3e0

File tree

33 files changed

+843
-69
lines changed

33 files changed

+843
-69
lines changed

admins/pageflow/accounts.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ module Pageflow
6767
end
6868

6969
controller do
70+
helper Pageflow::Admin::CutoffModesHelper
7071
helper Pageflow::Admin::FeaturesHelper
7172
helper Pageflow::Admin::FormHelper
7273
helper Pageflow::Admin::LocalesHelper

admins/pageflow/sites.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ module Pageflow
2929
:copyright_link_url,
3030
:copyright_link_label,
3131
:privacy_link_url,
32-
:home_url
32+
:home_url,
33+
:cutoff_mode_name
3334
] + permitted_admin_form_input_params
3435
end
3536

3637
controller do
38+
helper Pageflow::Admin::CutoffModesHelper
3739
helper Pageflow::Admin::FormHelper
3840

3941
before_create do |site|
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Pageflow
2+
module Admin
3+
# @api private
4+
module CutoffModesHelper
5+
def cutoff_modes_collection(config)
6+
config.cutoff_modes.names.map do |name|
7+
[t(name, scope: 'pageflow.cutoff_modes'), name]
8+
end
9+
end
10+
end
11+
end
12+
end

app/models/pageflow/draft_entry.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ def translations(scope = -> { self }, **)
2222
)
2323
end
2424

25+
def cutoff_mode_enabled_for?(_request)
26+
false
27+
end
28+
2529
def create_file!(file_type, attributes)
2630
check_foreign_key_custom_attributes(file_type.custom_attributes, attributes)
2731

app/models/pageflow/published_entry.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def translations(scope = -> { self }, include_noindex: false)
3434
end
3535
end
3636

37+
def cutoff_mode_enabled_for?(request)
38+
Pageflow.config.cutoff_modes.enabled_for?(self, request)
39+
end
40+
3741
def stylesheet_model
3842
custom_revision? ? revision : entry
3943
end

app/models/pageflow/site.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ class Site < ApplicationRecord
99
scope :with_home_url, -> { where.not(home_url: '') }
1010
scope :for_request, ->(request) { Pageflow.config.site_request_scope.call(all, request) }
1111

12-
validates :account, :presence => true
12+
validates :account, presence: true
13+
validates_inclusion_of :cutoff_mode_name, in: :available_cutoff_mode_names, allow_blank: true
1314

1415
delegate :enabled_feature_names, to: :account
1516

@@ -67,5 +68,11 @@ def self.ransackable_attributes(_auth_object = nil)
6768
def self.ransackable_associations(_auth_object = nil)
6869
%w[account]
6970
end
71+
72+
private
73+
74+
def available_cutoff_mode_names
75+
Pageflow.config_for(account).cutoff_modes.names
76+
end
7077
end
7178
end

app/views/admin/sites/_fields.html.erb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
<%= f.input :copyright_link_url %>
1313
<%= f.input :privacy_link_url %>
1414

15+
<% if cutoff_modes_collection(account_config).present? %>
16+
<%= f.input(:cutoff_mode_name,
17+
collection: cutoff_modes_collection(account_config),
18+
include_blank: t('pageflow.cutoff_modes.none')) %>
19+
<% end %>
20+
1521
<%= f.input :feeds_enabled, hint: t('pageflow.admin.sites.feeds_hint',
1622
site_host: @site&.persisted? ? @site.host : '<host>') %>
1723
<%= f.input :sitemap_enabled, hint: t('pageflow.admin.sites.sitemap_hint',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
json.(site, :cutoff_mode_name)
12
json.pretty_url pretty_site_url(site)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
de:
2+
pageflow:
3+
cutoff_modes:
4+
none: "(Kein)"
5+
pageflow_scrolled:
6+
editor:
7+
section_item:
8+
set_cutoff: "Paywall Grenze oberhalb setzen"
9+
reset_cutoff: "Paywall Grenze entfernen"
10+
cutoff: "Paywall Grenze"
11+
activerecord:
12+
attributes:
13+
pageflow/site:
14+
cutoff_mode_name: "Cutoff-Modus"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
en:
2+
pageflow:
3+
cutoff_modes:
4+
none: "(None)"
5+
pageflow_scrolled:
6+
editor:
7+
section_item:
8+
set_cutoff: "Set paywall cutoff above"
9+
reset_cutoff: "Remove paywall cutoff"
10+
cutoff: "Paywall cutoff"
11+
activerecord:
12+
attributes:
13+
pageflow/site:
14+
cutoff_mode_name: "Cutoff mode"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddCutoffModeNameToSites < ActiveRecord::Migration[5.2]
2+
def change
3+
add_column :pageflow_sites, :cutoff_mode_name, :string
4+
end
5+
end

entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,27 @@ def scrolled_entry_json_seed_script_tag(scrolled_entry, options = {})
2323
end
2424

2525
def scrolled_entry_json_seed(json, scrolled_entry, options = {})
26-
main_storyline = Storyline.all_for_revision(scrolled_entry.revision).first
27-
main_storyline ||= Storyline.new
26+
main_storyline = Storyline.all_for_revision(scrolled_entry.revision).first || Storyline.new
27+
sections = scrolled_entry_json_seed_sections(scrolled_entry, main_storyline)
2828

2929
json.partial!('pageflow_scrolled/entry_json_seed/entry',
30-
chapters: main_storyline.chapters,
3130
entry: scrolled_entry,
3231
entry_config: Pageflow.config_for(scrolled_entry),
33-
sections: main_storyline.sections,
34-
content_elements: main_storyline.content_elements,
32+
chapters: main_storyline.chapters,
33+
sections:,
34+
content_elements: main_storyline.content_elements.where(section: sections),
3535
widgets: scrolled_entry.resolve_widgets(insert_point: :react),
3636
options:)
3737
end
38+
39+
private
40+
41+
def scrolled_entry_json_seed_sections(scrolled_entry, main_storyline)
42+
if scrolled_entry.cutoff_mode_enabled_for?(request)
43+
main_storyline.sections_before_cutoff_section
44+
else
45+
main_storyline.sections
46+
end
47+
end
3848
end
3949
end

entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,19 @@ class Storyline < Pageflow::ApplicationRecord
2727
through: :sections
2828

2929
nested_revision_components :chapters
30+
31+
def sections_before_cutoff_section
32+
sections_before(cutoff_section)
33+
end
34+
35+
private
36+
37+
def sections_before(section)
38+
section ? sections[0...sections.index(section)] : sections
39+
end
40+
41+
def cutoff_section
42+
sections.find_by_perma_id(revision.configuration['cutoff_section_perma_id'])
43+
end
3044
end
3145
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {useEditorGlobals, useFakeXhr} from 'support';
2+
import '@testing-library/jest-dom/extend-expect';
3+
4+
describe('Cutoff', () => {
5+
useFakeXhr();
6+
7+
const {createEntry} = useEditorGlobals();
8+
9+
it('resets metadata configuration when deleting the cutoff section', () => {
10+
const entry = createEntry({
11+
metadata: {configuration: {cutoff_section_perma_id: 100}},
12+
sections: [
13+
{id: 1, permaId: 100},
14+
{id: 2, permaId: 101},
15+
]
16+
});
17+
18+
entry.sections.get(1).destroy();
19+
20+
expect(entry.metadata.configuration.get('cutoff_section_perma_id')).toBeUndefined();
21+
});
22+
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import {SectionItemView} from 'editor/views/SectionItemView';
2+
3+
import {useEditorGlobals, useFakeXhr, useReactBasedBackboneViews} from 'support';
4+
import userEvent from '@testing-library/user-event';
5+
import {useFakeTranslations} from 'pageflow/testHelpers';
6+
import '@testing-library/jest-dom/extend-expect';
7+
8+
describe('SectionItemView', () => {
9+
useFakeXhr();
10+
11+
useFakeTranslations({
12+
'pageflow_scrolled.editor.section_item.set_cutoff': 'Set cutoff point',
13+
'pageflow_scrolled.editor.section_item.reset_cutoff': 'Remove cutoff point',
14+
'pageflow_scrolled.editor.section_item.cutoff': 'Cutoff point',
15+
});
16+
17+
const {createEntry} = useEditorGlobals();
18+
const {render} = useReactBasedBackboneViews();
19+
20+
it('does not offer menu item to set cutoff section by default', () => {
21+
const entry = createEntry({
22+
sections: [
23+
{id: 1, permaId: 100}
24+
]
25+
});
26+
const view = new SectionItemView({
27+
entry,
28+
model: entry.sections.get(1)
29+
});
30+
31+
const {queryByRole} = render(view);
32+
33+
expect(queryByRole('link', {name: 'Set cutoff point'})).toBeNull();
34+
});
35+
36+
it('offers menu item to set cutoff section if site has cutoff mode', async () => {
37+
const entry = createEntry({
38+
site: {
39+
cutoff_mode_name: 'subscription_headers'
40+
},
41+
sections: [
42+
{id: 1, permaId: 100}
43+
]
44+
});
45+
const view = new SectionItemView({
46+
entry,
47+
model: entry.sections.get(1)
48+
});
49+
50+
const user = userEvent.setup();
51+
const {getByRole} = render(view);
52+
await user.click(getByRole('link', {name: 'Set cutoff point'}));
53+
54+
expect(entry.metadata.configuration.get('cutoff_section_perma_id')).toEqual(100);
55+
});
56+
57+
it('offers menu item to reset cutoff section if site has cutoff mode', async () => {
58+
const entry = createEntry({
59+
site: {
60+
cutoff_mode_name: 'subscription_headers'
61+
},
62+
metadata: {configuration: {cutoff_section_perma_id: 101}},
63+
sections: [
64+
{id: 1, permaId: 100},
65+
{id: 2, permaId: 101}
66+
]
67+
});
68+
const view = new SectionItemView({
69+
entry,
70+
model: entry.sections.get(2)
71+
});
72+
73+
const user = userEvent.setup();
74+
const {getByRole} = render(view);
75+
await user.click(getByRole('link', {name: 'Remove cutoff point'}));
76+
77+
expect(entry.metadata.configuration.get('cutoff_section_perma_id')).toBeUndefined();
78+
});
79+
80+
it('updates menu item when cutoff section changes', () => {
81+
const entry = createEntry({
82+
site: {
83+
cutoff_mode_name: 'subscription_headers'
84+
},
85+
sections: [
86+
{id: 1, permaId: 100},
87+
{id: 2, permaId: 101}
88+
]
89+
});
90+
const view = new SectionItemView({
91+
entry,
92+
model: entry.sections.get(2)
93+
});
94+
95+
const {queryByRole} = render(view);
96+
entry.metadata.configuration.set('cutoff_section_perma_id', 101)
97+
98+
expect(queryByRole('link', {name: 'Remove cutoff point'})).not.toBeNull();
99+
});
100+
101+
it('renders cutoff indicator', () => {
102+
const entry = createEntry({
103+
site: {
104+
cutoff_mode_name: 'subscription_headers'
105+
},
106+
sections: [
107+
{id: 1, permaId: 100},
108+
{id: 2, permaId: 101}
109+
]
110+
});
111+
const view = new SectionItemView({
112+
entry,
113+
model: entry.sections.get(2)
114+
});
115+
116+
const {queryByText} = render(view);
117+
entry.metadata.configuration.set('cutoff_section_perma_id', 101)
118+
119+
expect(queryByText('Cutoff point')).toBeVisible();
120+
});
121+
122+
it('does not render cutoff indicator if cutoff section not set', () => {
123+
const entry = createEntry({
124+
site: {
125+
cutoff_mode_name: 'subscription_headers'
126+
},
127+
sections: [
128+
{id: 1, permaId: 100},
129+
{id: 2, permaId: 101}
130+
]
131+
});
132+
const view = new SectionItemView({
133+
entry,
134+
model: entry.sections.get(2)
135+
});
136+
137+
const {queryByText} = render(view);
138+
139+
expect(queryByText('Cutoff point')).not.toBeVisible();
140+
});
141+
142+
it('does not render cutoff indicator if site does not have cutoff mode', () => {
143+
const entry = createEntry({
144+
metadata: {configuration: {cutoff_section_perma_id: 101}},
145+
sections: [
146+
{id: 1, permaId: 100},
147+
{id: 2, permaId: 101}
148+
]
149+
});
150+
const view = new SectionItemView({
151+
entry,
152+
model: entry.sections.get(2)
153+
});
154+
155+
const {queryByText} = render(view);
156+
157+
expect(queryByText('Cutoff point')).not.toBeVisible();
158+
});
159+
});

entry_types/scrolled/package/spec/support/useEditorGlobals.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {editor, FilesCollection} from 'pageflow/editor';
1+
import {editor, FilesCollection, Site} from 'pageflow/editor';
22
import {ScrolledEntry} from 'editor/models/ScrolledEntry';
33

44
import {setupGlobals} from 'pageflow/testHelpers';
@@ -23,12 +23,15 @@ export function useEditorGlobals() {
2323
return {
2424
createEntry(options) {
2525
const {
26+
metadata,
2627
imageFiles, videoFiles, audioFiles, textTrackFiles,
28+
site,
2729
...seedOptions
2830
} = options;
2931

3032
const {entry} = setGlobals({
31-
entry: factories.entry(ScrolledEntry, {}, {
33+
entry: factories.entry(ScrolledEntry, {metadata}, {
34+
site: new Site(site),
3235
files: FilesCollection.createForFileTypes(
3336
[
3437
editor.fileTypes.findByCollectionName('image_files'),

0 commit comments

Comments
 (0)