diff --git a/app/views/pageflow/editor/sites/_site.json.jbuilder b/app/views/pageflow/editor/sites/_site.json.jbuilder index d995099a68..0769da9858 100644 --- a/app/views/pageflow/editor/sites/_site.json.jbuilder +++ b/app/views/pageflow/editor/sites/_site.json.jbuilder @@ -1 +1,2 @@ +json.(site, :cutoff_mode_name) json.pretty_url pretty_site_url(site) diff --git a/config/locales/new/cutoff_modes.de.yml b/config/locales/new/cutoff_modes.de.yml index 9f7fbbd768..2bb508d439 100644 --- a/config/locales/new/cutoff_modes.de.yml +++ b/config/locales/new/cutoff_modes.de.yml @@ -2,6 +2,12 @@ de: pageflow: cutoff_modes: none: "(Kein)" + pageflow_scrolled: + editor: + section_item: + set_cutoff: "Paywall Grenze oberhalb setzen" + reset_cutoff: "Paywall Grenze entfernen" + cutoff: "Paywall Grenze" activerecord: attributes: pageflow/site: diff --git a/config/locales/new/cutoff_modes.en.yml b/config/locales/new/cutoff_modes.en.yml index a850f2bdc8..f97dc428da 100644 --- a/config/locales/new/cutoff_modes.en.yml +++ b/config/locales/new/cutoff_modes.en.yml @@ -2,6 +2,12 @@ en: pageflow: cutoff_modes: none: "(None)" + pageflow_scrolled: + editor: + section_item: + set_cutoff: "Set paywall cutoff above" + reset_cutoff: "Remove paywall cutoff" + cutoff: "Paywall cutoff" activerecord: attributes: pageflow/site: diff --git a/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js b/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js new file mode 100644 index 0000000000..2927d2ad8b --- /dev/null +++ b/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js @@ -0,0 +1,159 @@ +import {SectionItemView} from 'editor/views/SectionItemView'; + +import {useEditorGlobals, useFakeXhr, useReactBasedBackboneViews} from 'support'; +import userEvent from '@testing-library/user-event'; +import {useFakeTranslations} from 'pageflow/testHelpers'; +import '@testing-library/jest-dom/extend-expect'; + +describe('SectionItemView', () => { + useFakeXhr(); + + useFakeTranslations({ + 'pageflow_scrolled.editor.section_item.set_cutoff': 'Set cutoff point', + 'pageflow_scrolled.editor.section_item.reset_cutoff': 'Remove cutoff point', + 'pageflow_scrolled.editor.section_item.cutoff': 'Cutoff point', + }); + + const {createEntry} = useEditorGlobals(); + const {render} = useReactBasedBackboneViews(); + + it('does not offer menu item to set cutoff section by default', () => { + const entry = createEntry({ + sections: [ + {id: 1, permaId: 100} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(1) + }); + + const {queryByRole} = render(view); + + expect(queryByRole('link', {name: 'Set cutoff point'})).toBeNull(); + }); + + it('offers menu item to set cutoff section if site has cutoff mode', async () => { + const entry = createEntry({ + site: { + cutoff_mode_name: 'subscription_headers' + }, + sections: [ + {id: 1, permaId: 100} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(1) + }); + + const user = userEvent.setup(); + const {getByRole} = render(view); + await user.click(getByRole('link', {name: 'Set cutoff point'})); + + expect(entry.metadata.configuration.get('cutoff_section_perma_id')).toEqual(100); + }); + + it('offers menu item to reset cutoff section if site has cutoff mode', async () => { + const entry = createEntry({ + site: { + cutoff_mode_name: 'subscription_headers' + }, + metadata: {configuration: {cutoff_section_perma_id: 101}}, + sections: [ + {id: 1, permaId: 100}, + {id: 2, permaId: 101} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(2) + }); + + const user = userEvent.setup(); + const {getByRole} = render(view); + await user.click(getByRole('link', {name: 'Remove cutoff point'})); + + expect(entry.metadata.configuration.get('cutoff_section_perma_id')).toBeUndefined(); + }); + + it('updates menu item when cutoff section changes', () => { + const entry = createEntry({ + site: { + cutoff_mode_name: 'subscription_headers' + }, + sections: [ + {id: 1, permaId: 100}, + {id: 2, permaId: 101} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(2) + }); + + const {queryByRole} = render(view); + entry.metadata.configuration.set('cutoff_section_perma_id', 101) + + expect(queryByRole('link', {name: 'Remove cutoff point'})).not.toBeNull(); + }); + + it('renders cutoff indicator', () => { + const entry = createEntry({ + site: { + cutoff_mode_name: 'subscription_headers' + }, + sections: [ + {id: 1, permaId: 100}, + {id: 2, permaId: 101} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(2) + }); + + const {queryByText} = render(view); + entry.metadata.configuration.set('cutoff_section_perma_id', 101) + + expect(queryByText('Cutoff point')).toBeVisible(); + }); + + it('does not render cutoff indicator if cutoff section not set', () => { + const entry = createEntry({ + site: { + cutoff_mode_name: 'subscription_headers' + }, + sections: [ + {id: 1, permaId: 100}, + {id: 2, permaId: 101} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(2) + }); + + const {queryByText} = render(view); + + expect(queryByText('Cutoff point')).not.toBeVisible(); + }); + + it('does not render cutoff indicator if site does not have cutoff mode', () => { + const entry = createEntry({ + metadata: {configuration: {cutoff_section_perma_id: 101}}, + sections: [ + {id: 1, permaId: 100}, + {id: 2, permaId: 101} + ] + }); + const view = new SectionItemView({ + entry, + model: entry.sections.get(2) + }); + + const {queryByText} = render(view); + + expect(queryByText('Cutoff point')).not.toBeVisible(); + }); +}); diff --git a/entry_types/scrolled/package/spec/support/useEditorGlobals.js b/entry_types/scrolled/package/spec/support/useEditorGlobals.js index 8290d0dfdd..499c779d44 100644 --- a/entry_types/scrolled/package/spec/support/useEditorGlobals.js +++ b/entry_types/scrolled/package/spec/support/useEditorGlobals.js @@ -1,4 +1,4 @@ -import {editor, FilesCollection} from 'pageflow/editor'; +import {editor, FilesCollection, Site} from 'pageflow/editor'; import {ScrolledEntry} from 'editor/models/ScrolledEntry'; import {setupGlobals} from 'pageflow/testHelpers'; @@ -23,12 +23,15 @@ export function useEditorGlobals() { return { createEntry(options) { const { + metadata, imageFiles, videoFiles, audioFiles, textTrackFiles, + site, ...seedOptions } = options; const {entry} = setGlobals({ - entry: factories.entry(ScrolledEntry, {}, { + entry: factories.entry(ScrolledEntry, {metadata}, { + site: new Site(site), files: FilesCollection.createForFileTypes( [ editor.fileTypes.findByCollectionName('image_files'), diff --git a/entry_types/scrolled/package/src/editor/models/Cutoff.js b/entry_types/scrolled/package/src/editor/models/Cutoff.js new file mode 100644 index 0000000000..baff2d8591 --- /dev/null +++ b/entry_types/scrolled/package/src/editor/models/Cutoff.js @@ -0,0 +1,27 @@ +import {Object} from 'pageflow/ui'; + +export const Cutoff = Object.extend({ + initialize({entry}) { + this.entry = entry; + + this.listenTo(this.entry.metadata.configuration, + 'change:cutoff_section_perma_id', + () => this.trigger('change')); + }, + + isEnabled() { + return !!this.entry.site.get('cutoff_mode_name'); + }, + + isAtSection(section) { + return this.entry.metadata.configuration.get('cutoff_section_perma_id') === section.get('permaId'); + }, + + reset() { + this.entry.metadata.configuration.unset('cutoff_section_perma_id'); + }, + + setSection(section) { + this.entry.metadata.configuration.set('cutoff_section_perma_id', section.get('permaId')); + } +}); diff --git a/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js b/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js index 9d7ec14f1c..37b69c6ade 100644 --- a/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js +++ b/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js @@ -4,6 +4,7 @@ import I18n from 'i18n-js'; import {ConsentVendors} from '../ConsentVendors'; import {ChaptersCollection, SectionsCollection, ContentElementsCollection} from '../../collections'; import {ContentElement} from '../ContentElement'; +import {Cutoff} from '../Cutoff'; import {insertContentElement} from './insertContentElement'; import {moveContentElement} from './moveContentElement'; @@ -12,6 +13,7 @@ import {deleteContentElement} from './deleteContentElement'; export const ScrolledEntry = Entry.extend({ setupFromEntryTypeSeed(seed) { this.consentVendors = new ConsentVendors({hostMatchers: seed.consentVendorHostMatchers}); + this.cutoff = new Cutoff({entry: this}); this.contentElements = new ContentElementsCollection(seed.collections.contentElements); this.sections = new SectionsCollection(seed.collections.sections, diff --git a/entry_types/scrolled/package/src/editor/views/ChapterItemView.js b/entry_types/scrolled/package/src/editor/views/ChapterItemView.js index 0a8b08b325..524f8ecd51 100644 --- a/entry_types/scrolled/package/src/editor/views/ChapterItemView.js +++ b/entry_types/scrolled/package/src/editor/views/ChapterItemView.js @@ -10,7 +10,7 @@ import styles from './ChapterItemView.module.css'; export const ChapterItemView = Marionette.Layout.extend({ tagName: 'li', - className: `${styles.root} ${styles.withTransitions}`, + className: `${styles.root}`, mixins: [modelLifecycleTrackingView({classNames: styles})], diff --git a/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css b/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css index f1058399b3..7cc075a10e 100644 --- a/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css +++ b/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css @@ -58,23 +58,6 @@ min-height: 20px; } -.withTransitions .sections { - margin-top: 20px; -} - -.root:first-child .sections { - margin-top: 10px; -} - -.sections > :global(.sortable-placeholder) { - margin-top: 25px; - margin-bottom: 22px; -} - -.root:first-child .sections > li:first-child + :global(.sortable-placeholder) { - margin-top: 16px; -} - .creating .creatingIndicator { display: block; } .destroying .destroyingIndicator { display: block; } .failed .failedIndicator { display: block; } diff --git a/entry_types/scrolled/package/src/editor/views/SectionItemView.js b/entry_types/scrolled/package/src/editor/views/SectionItemView.js index 61861e865e..f071ad8e47 100644 --- a/entry_types/scrolled/package/src/editor/views/SectionItemView.js +++ b/entry_types/scrolled/package/src/editor/views/SectionItemView.js @@ -5,7 +5,6 @@ import {modelLifecycleTrackingView, DropDownButtonView} from 'pageflow/editor'; import {cssModulesUtils} from 'pageflow/ui'; import {SectionThumbnailView} from './SectionThumbnailView' -import {getAvailableTransitionNames} from 'pageflow-scrolled/frontend'; import arrowsIcon from './images/arrows.svg'; @@ -13,32 +12,39 @@ import styles from './SectionItemView.module.css'; export const SectionItemView = Marionette.ItemView.extend({ tagName: 'li', - className: `${styles.root} ${styles.withTransition}`, + className: `${styles.root}`, mixins: [modelLifecycleTrackingView({classNames: styles})], template: (data) => ` +