diff --git a/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb b/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb index cb7d45ee7..c5a643ae7 100644 --- a/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +++ b/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb @@ -17,6 +17,7 @@ def scrolled_entry_editor_json_seed(json, scrolled_entry) scrolled_entry, skip_files: true, skip_i18n: true, + include_hidden_sections: true, include_unused_additional_seed_data: true) end diff --git a/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb b/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb index 46dcfe906..6ebc86d40 100644 --- a/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +++ b/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb @@ -24,12 +24,12 @@ def scrolled_entry_json_seed_script_tag(scrolled_entry, options = {}) def scrolled_entry_json_seed(json, scrolled_entry, options = {}) main_storyline = Storyline.all_for_revision(scrolled_entry.revision).first || Storyline.new - sections = scrolled_entry_json_seed_sections(scrolled_entry, main_storyline) + sections = scrolled_entry_json_seed_sections(scrolled_entry, main_storyline, options) json.partial!('pageflow_scrolled/entry_json_seed/entry', entry: scrolled_entry, entry_config: Pageflow.config_for(scrolled_entry), - chapters: main_storyline.chapters, + chapters: scrolled_entry_json_seed_chapters(main_storyline, options), sections:, content_elements: main_storyline.content_elements.where(section: sections), widgets: scrolled_entry.resolve_widgets(insert_point: :react), @@ -38,12 +38,43 @@ def scrolled_entry_json_seed(json, scrolled_entry, options = {}) private - def scrolled_entry_json_seed_sections(scrolled_entry, main_storyline) - if scrolled_entry.cutoff_mode_enabled_for?(request) - main_storyline.sections_before_cutoff_section - else - main_storyline.sections + def scrolled_entry_json_seed_sections(scrolled_entry, main_storyline, options) + sections = + if scrolled_entry.cutoff_mode_enabled_for?(request) + main_storyline.sections_before_cutoff_section + else + main_storyline.sections + end + + return sections if options[:include_hidden_sections] + + sections.reject { |section| section.configuration['hidden'] } + end + + def scrolled_entry_json_seed_chapters(main_storyline, options) + return main_storyline.chapters if options[:include_hidden_sections] + + has_visible_sections, has_hidden_sections = + scrolled_entry_json_seed_chapter_section_visibilites(main_storyline) + + main_storyline.chapters.reject do |chapter| + has_hidden_sections[chapter.id] && !has_visible_sections[chapter.id] end end + + def scrolled_entry_json_seed_chapter_section_visibilites(main_storyline) + has_visible_sections = [] + has_hidden_sections = [] + + main_storyline.sections.each do |section| + if section.configuration['hidden'] + has_hidden_sections[section.chapter_id] = true + else + has_visible_sections[section.chapter_id] = true + end + end + + [has_visible_sections, has_hidden_sections] + end end end diff --git a/entry_types/scrolled/config/locales/new/hide_section.de.yml b/entry_types/scrolled/config/locales/new/hide_section.de.yml new file mode 100644 index 000000000..0909fb3b1 --- /dev/null +++ b/entry_types/scrolled/config/locales/new/hide_section.de.yml @@ -0,0 +1,7 @@ +de: + pageflow_scrolled: + editor: + section_item: + hide: Außerhalb des Editors ausblenden + show: Außerhalb des Editors einblenden + hidden: Nur im Editor sichtbar diff --git a/entry_types/scrolled/config/locales/new/hide_section.en.yml b/entry_types/scrolled/config/locales/new/hide_section.en.yml new file mode 100644 index 000000000..78dda93f7 --- /dev/null +++ b/entry_types/scrolled/config/locales/new/hide_section.en.yml @@ -0,0 +1,7 @@ +en: + pageflow_scrolled: + editor: + section_item: + hide: Hide outside of the editor + show: Show outside of the editor + hidden: Only visible in editor diff --git a/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js b/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js index 2927d2ad8..e792def82 100644 --- a/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js +++ b/entry_types/scrolled/package/spec/editor/views/SectionItemView-spec.js @@ -9,6 +9,8 @@ describe('SectionItemView', () => { useFakeXhr(); useFakeTranslations({ + 'pageflow_scrolled.editor.section_item.hide': 'Hide', + 'pageflow_scrolled.editor.section_item.show': 'Show', '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', @@ -17,6 +19,29 @@ describe('SectionItemView', () => { const {createEntry} = useEditorGlobals(); const {render} = useReactBasedBackboneViews(); + it('offer menu item to hide and show section', async () => { + const entry = createEntry({ + sections: [ + {id: 1, permaId: 100} + ] + }); + const section = entry.sections.get(1) + const view = new SectionItemView({ + entry, + model: section + }); + + const user = userEvent.setup(); + const {getByRole} = render(view); + await user.click(getByRole('link', {name: 'Hide'})); + + expect(section.configuration.get('hidden')).toEqual(true); + + await user.click(getByRole('link', {name: 'Show'})); + + expect(section.configuration.get('hidden')).toBeUndefined(); + }); + it('does not offer menu item to set cutoff section by default', () => { const entry = createEntry({ sections: [ diff --git a/entry_types/scrolled/package/src/editor/views/SectionItemView.js b/entry_types/scrolled/package/src/editor/views/SectionItemView.js index 6546623dc..8211e96a9 100644 --- a/entry_types/scrolled/package/src/editor/views/SectionItemView.js +++ b/entry_types/scrolled/package/src/editor/views/SectionItemView.js @@ -7,6 +7,7 @@ import {cssModulesUtils} from 'pageflow/ui'; import {SectionThumbnailView} from './SectionThumbnailView' import arrowsIcon from './images/arrows.svg'; +import hiddenIcon from './images/hidden.svg'; import styles from './SectionItemView.module.css'; @@ -38,6 +39,11 @@ export const SectionItemView = Marionette.ItemView.extend({ + `, @@ -83,11 +89,16 @@ export const SectionItemView = Marionette.ItemView.extend({ this.listenTo(this.options.entry.cutoff, 'change', () => { this.updateCutoffIndicator(); }); + + this.listenTo(this.model.configuration, 'change:hidden', () => { + this.updateHidden(); + }); }, onRender() { this.updateTransition(); this.updateCutoffIndicator(); + this.updateHidden(); if (this.updateActive()) { setTimeout(() => this.$el[0].scrollIntoView({block: 'nearest'}), 10) @@ -124,6 +135,10 @@ export const SectionItemView = Marionette.ItemView.extend({ this.model.chapter.insertSection({after: this.model}) })); + dropDownMenuItems.add(new HideShowMenuItem({}, { + section: this.model + })); + if (this.options.entry.cutoff.isEnabled()) { dropDownMenuItems.add(new CutoffMenuItem({}, { cutoff: this.options.entry.cutoff, @@ -155,16 +170,16 @@ export const SectionItemView = Marionette.ItemView.extend({ ); }, - cutoffModeEnabled() { - return !!this.options.entry.site.get('cutoff_mode_name'); - }, - updateActive() { const active = this.options.entry.sections.indexOf(this.model) === this.options.entry.get('currentSectionIndex'); this.$el.toggleClass(styles.active, active); return active; + }, + + updateHidden() { + this.$el.toggleClass(styles.hidden, !!this.model.configuration.get('hidden')); } }); @@ -204,3 +219,29 @@ const CutoffMenuItem = Backbone.Model.extend({ )); } }); + +const HideShowMenuItem = Backbone.Model.extend({ + initialize: function(attributes, {section}) { + this.section = section; + + this.listenTo(section.configuration, 'change:hidden', this.update); + this.update(); + }, + + selected() { + if (this.section.configuration.get('hidden')) { + this.section.configuration.unset('hidden') + } + else { + this.section.configuration.set('hidden', true) + } + }, + + update() { + this.set('label', I18n.t( + this.section.configuration.get('hidden') ? + 'pageflow_scrolled.editor.section_item.show' : + 'pageflow_scrolled.editor.section_item.hide' + )); + } +}); diff --git a/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css b/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css index 3c923d7f1..b7d112ad8 100644 --- a/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css +++ b/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css @@ -9,6 +9,18 @@ composes: sectionWithTransition from './outline.module.css'; } +.hiddenIndicator { + display: none; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.hidden .hiddenIndicator { + display: block; +} + .outline { position: relative; border: solid selectionWidth transparent; @@ -29,6 +41,8 @@ align-items: center; gap: 5px; padding: 5px 0; + font-weight: 500; + color: var(--ui-error-color); } .cutoffIndicator::before, @@ -40,9 +54,12 @@ .thumbnailContainer { position: relative; + background-color: var(--ui-surface-color); } -.thumbnail {} +.hidden .thumbnail > * > * { + opacity: 0.3; +} .clickMask { position: absolute; diff --git a/entry_types/scrolled/package/src/editor/views/images/hidden.svg b/entry_types/scrolled/package/src/editor/views/images/hidden.svg new file mode 100644 index 000000000..1997f6380 --- /dev/null +++ b/entry_types/scrolled/package/src/editor/views/images/hidden.svg @@ -0,0 +1,3 @@ + + + diff --git a/entry_types/scrolled/spec/helpers/pageflow_scrolled/editor/entry_json_seed_helper_spec.rb b/entry_types/scrolled/spec/helpers/pageflow_scrolled/editor/entry_json_seed_helper_spec.rb index 8a74df52d..be93d915b 100644 --- a/entry_types/scrolled/spec/helpers/pageflow_scrolled/editor/entry_json_seed_helper_spec.rb +++ b/entry_types/scrolled/spec/helpers/pageflow_scrolled/editor/entry_json_seed_helper_spec.rb @@ -31,6 +31,23 @@ def render(helper, entry) }) end + it 'renders hidden chapters and sections' do + entry = create(:draft_entry, type_name: 'scrolled') + chapter = create(:scrolled_chapter, revision: entry.revision) + section = create(:section, chapter:, configuration: {hidden: true}) + content_element = create(:content_element, section:) + + result = render(helper, entry) + + expect(result) + .to include_json(collections: { + entries: [], + chapters: [{id: chapter.id}], + sections: [{id: section.id}], + contentElements: [{id: content_element.id}], + }) + end + it 'does not render files' do entry = create(:draft_entry, type_name: 'scrolled') create_used_file(:image_file, entry: entry) diff --git a/entry_types/scrolled/spec/helpers/pageflow_scrolled/entry_json_seed_helper_spec.rb b/entry_types/scrolled/spec/helpers/pageflow_scrolled/entry_json_seed_helper_spec.rb index 836decbc0..b3b2e8e0c 100644 --- a/entry_types/scrolled/spec/helpers/pageflow_scrolled/entry_json_seed_helper_spec.rb +++ b/entry_types/scrolled/spec/helpers/pageflow_scrolled/entry_json_seed_helper_spec.rb @@ -109,6 +109,39 @@ def render(helper, entry, options = {}) JSON.parse(result).dig('collections', 'chapters').map { |chapter| chapter['id'] } ).to eq([chapter1.id, chapter2.id]) end + + it 'filters out chapters with only hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + chapter1 = create(:scrolled_chapter, revision: entry.revision) + create(:section, chapter: chapter1) + create(:section, chapter: chapter1, configuration: {hidden: true}) + chapter2 = create(:scrolled_chapter, revision: entry.revision) + create(:section, chapter: chapter2, configuration: {hidden: true}) + create(:section, chapter: chapter2, configuration: {hidden: true}) + chapter3 = create(:scrolled_chapter, revision: entry.revision) + + result = render(helper, entry) + + expect( + JSON.parse(result).dig('collections', 'chapters').map { |chapter| chapter['id'] } + ).to eq([chapter1.id, chapter3.id]) + end + + it 'supports including chapters with only hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + chapter1 = create(:scrolled_chapter, revision: entry.revision) + create(:section, chapter: chapter1) + create(:section, chapter: chapter1, configuration: {hidden: true}) + chapter2 = create(:scrolled_chapter, revision: entry.revision) + create(:section, chapter: chapter2, configuration: {hidden: true}) + create(:section, chapter: chapter2, configuration: {hidden: true}) + + result = render(helper, entry, include_hidden_sections: true) + + expect( + JSON.parse(result).dig('collections', 'chapters').map { |chapter| chapter['id'] } + ).to eq([chapter1.id, chapter2.id]) + end end context 'sections' do @@ -305,6 +338,34 @@ def render(helper, entry, options = {}) JSON.parse(result).dig('collections', 'sections').map { |s| s['id'] } ).to eq([section11.id, section12.id, section21.id, section22.id]) end + + it 'filters out hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + + chapter1 = create(:scrolled_chapter, position: 1, revision: entry.revision) + section11 = create(:section, chapter: chapter1, position: 1) + create(:section, chapter: chapter1, position: 2, configuration: {hidden: true}) + + result = render(helper, entry) + + expect( + JSON.parse(result).dig('collections', 'sections').map { |s| s['id'] } + ).to eq([section11.id]) + end + + it 'supports including hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + + chapter1 = create(:scrolled_chapter, position: 1, revision: entry.revision) + section11 = create(:section, chapter: chapter1, position: 1) + section12 = create(:section, chapter: chapter1, position: 2, configuration: {hidden: true}) + + result = render(helper, entry, include_hidden_sections: true) + + expect( + JSON.parse(result).dig('collections', 'sections').map { |s| s['id'] } + ).to eq([section11.id, section12.id]) + end end context 'content_elements' do @@ -409,6 +470,38 @@ def render(helper, entry, options = {}) JSON.parse(result).dig('collections', 'contentElements').map { |c| c['id'] } ).to eq([content_element11.id, content_element12.id]) end + + it 'supports filtering content elements of hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + + chapter1 = create(:scrolled_chapter, revision: entry.revision) + section11 = create(:section, chapter: chapter1, configuration: {hidden: true}) + create(:content_element, section: section11) + section12 = create(:section, chapter: chapter1) + content_element12 = create(:content_element, section: section12) + + result = render(helper, entry) + + expect( + JSON.parse(result).dig('collections', 'contentElements').map { |c| c['id'] } + ).to eq([content_element12.id]) + end + + it 'supports including content elements of hidden sections' do + entry = create(:published_entry, type_name: 'scrolled') + + chapter1 = create(:scrolled_chapter, revision: entry.revision) + section11 = create(:section, chapter: chapter1, configuration: {hidden: true}) + content_element11 = create(:content_element, section: section11) + section12 = create(:section, chapter: chapter1) + content_element12 = create(:content_element, section: section12) + + result = render(helper, entry, include_hidden_sections: true) + + expect( + JSON.parse(result).dig('collections', 'contentElements').map { |c| c['id'] } + ).to eq([content_element11.id, content_element12.id]) + end end describe 'widgets' do