From 311b63d63e9df77cd04fc6b612373dfe2654059a Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 23 Oct 2024 14:07:45 +0200 Subject: [PATCH 1/5] Allow using alternative model for input view attribute bindings Make it easier to bind `disabled` or `visible` state to transient state of a content element. REDMINE-20848 --- .../spec/ui/views/mixins/inputView-spec.js | 32 +++++++++++++++++++ .../src/ui/views/mixins/attributeBinding.js | 8 +++-- package/src/ui/views/mixins/inputView.js | 6 ++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/package/spec/ui/views/mixins/inputView-spec.js b/package/spec/ui/views/mixins/inputView-spec.js index 44048a9dc8..b4bec5cc3b 100644 --- a/package/spec/ui/views/mixins/inputView-spec.js +++ b/package/spec/ui/views/mixins/inputView-spec.js @@ -396,6 +396,23 @@ describe('pageflow.inputView', () => { }); }); + describe('with disabledBindingModel option', () => { + it('adds listener to passed model instead', () => { + const otherModel = new Backbone.Model(); + var view = createInputViewWithInput({ + model: new Backbone.Model(), + disabledBinding: 'disable', + disabledBindingModel: otherModel, + disabledBindingValue: true + }); + + view.render(); + otherModel.set({disable: true}); + + expect(view.ui.input).toHaveAttr('disabled'); + }); + }); + describe('with function for disabled option', () => { it('disables input when function returns true', () => { var view = createInputViewWithInput({ @@ -433,6 +450,21 @@ describe('pageflow.inputView', () => { expect(view.ui.input).not.toHaveAttr('disabled'); }); + describe('with disabledBindingModel', () => { + it('passes value from binding model to function', () => { + var otherModel = new Backbone.Model({state: 'disabled'}) + var view = createInputViewWithInput({ + model: new Backbone.Model({}), + disabledBindingModel: otherModel, + disabledBinding: 'state', + disabled: function(value) { return value === 'disabled'; } + }); + + view.render(); + + expect(view.ui.input).toHaveAttr('disabled'); + }); + }); describe('with multiple binding attributes', () => { it('passes array of values to function', () => { const disabledFunction = jest.fn(); diff --git a/package/src/ui/views/mixins/attributeBinding.js b/package/src/ui/views/mixins/attributeBinding.js index ea9c5c0ba9..6fd377b7bd 100644 --- a/package/src/ui/views/mixins/attributeBinding.js +++ b/package/src/ui/views/mixins/attributeBinding.js @@ -11,11 +11,12 @@ export const attributeBinding = { setupAttributeBinding: function(optionName, updateMethod, normalize = value => value) { const binding = this.options[`${optionName}Binding`]; + const model = this.options[`${optionName}BindingModel`] || this.model; const view = this; if (binding) { _.flatten([binding]).forEach(attribute => { - this.listenTo(this.model, 'change:' + attribute, update); + this.listenTo(model, 'change:' + attribute, update); }); } @@ -28,11 +29,12 @@ export const attributeBinding = { getAttributeBoundOption(optionName, normalize = value => value) { const binding = this.options[`${optionName}Binding`]; + const model = this.options[`${optionName}BindingModel`] || this.model; const bindingValueOptionName = `${optionName}BindingValue`; const value = Array.isArray(binding) ? - binding.map(attribute => this.model.get(attribute)) : - this.model.get(binding); + binding.map(attribute => model.get(attribute)) : + model.get(binding); if (bindingValueOptionName in this.options) { return value === this.options[bindingValueOptionName]; diff --git a/package/src/ui/views/mixins/inputView.js b/package/src/ui/views/mixins/inputView.js index 5e7241307c..65f8120907 100644 --- a/package/src/ui/views/mixins/inputView.js +++ b/package/src/ui/views/mixins/inputView.js @@ -113,6 +113,9 @@ import {attributeBinding} from './attributeBinding'; * Input will be disabled whenever the value of the `disabledBinding` * attribute equals the value of this option. * + * @param {Backbone.Model} [options.disabledBindingModel] + * Alternative model to bind to. + * * @param {string|string[]} [options.visibleBinding] * Name of an attribute to control whether the input is visible. If * the `visible` and `visibleBindingValue` options are not set, @@ -129,6 +132,9 @@ import {attributeBinding} from './attributeBinding'; * Input will be visible whenever the value of the `visibleBinding` * attribute equals the value of this option. * + * @param {Backbone.Model} [options.visibleBindingModel] + * Alternative model to bind to. + * * @mixin */ export const inputView = { From dbd57a0db7bfa6c134365e0f28cb62a983cb417f Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 23 Oct 2024 14:10:10 +0200 Subject: [PATCH 2/5] Let widgets types define dynamic configuration editor tab group We want to show certain groups of options only if a certain widget type is enabled. For any group defined by any widget type, initially define empty stub groups, such that the group can be used even when no widget defining the group is currently active. REDMINE-20848 --- .../collections/widgetsCollection-spec.js | 126 ++++++++++++++++++ package/src/editor/api/WidgetType.js | 14 +- package/src/editor/api/WidgetTypes.js | 6 + .../editor/collections/WidgetsCollection.js | 16 ++- .../editor/initializers/setupCollections.js | 2 + package/src/editor/models/Widget.js | 5 + 6 files changed, 167 insertions(+), 2 deletions(-) diff --git a/package/spec/editor/collections/widgetsCollection-spec.js b/package/spec/editor/collections/widgetsCollection-spec.js index 43705724c4..416f316e8e 100644 --- a/package/spec/editor/collections/widgetsCollection-spec.js +++ b/package/spec/editor/collections/widgetsCollection-spec.js @@ -1,4 +1,5 @@ import {WidgetsCollection} from 'editor/collections/WidgetsCollection'; +import {CheckBoxInputView, ConfigurationEditorTabView} from 'pageflow/ui'; import {factories} from '$support'; describe('WidgetsCollection', () => { @@ -47,4 +48,129 @@ describe('WidgetsCollection', () => { expect(subsetCollection.pluck('type_name')).toEqual(['consent_bar']); }); + + it('can define configuration editor tab view groups of widget types', () => { + const widgetTypes = factories.widgetTypes( + [ + { + role: 'inlineFileRights', + name: 'textInlineFileRights', + insertPoint: 'react' + } + ], + w => { + w.register('textInlineFileRights', { + configurationEditorTabViewGroups: { + ContentElementInlineFileRightsSettings() { + this.input('showBackdrop', CheckBoxInputView); + } + } + }); + } + ); + const widgets = new WidgetsCollection([ + {type_name: 'textInlineFileRights', role: 'inlineFileRights'}, + ], {widgetTypes}); + const groups = new ConfigurationEditorTabView.Groups(); + widgets.subject = factories.entry(); + + widgets.setupConfigurationEditorTabViewGroups(groups); + const context = { + input: jest.fn() + }; + groups.apply( + 'ContentElementInlineFileRightsSettings', + context + ); + + expect(context.input).toHaveBeenCalledWith('showBackdrop', CheckBoxInputView); + }); + + it('defines stub configuration editor tab view groups for unused widget types', () => { + const widgetTypes = factories.widgetTypes( + [ + { + role: 'inlineFileRights', + name: 'iconInlineFileRights', + insertPoint: 'react' + }, + { + role: 'inlineFileRights', + name: 'textInlineFileRights', + insertPoint: 'react' + } + ], + w => { + w.register('textInlineFileRights', { + configurationEditorTabViewGroups: { + ContentElementInlineFileRightsSettings() { + this.input('showBackdrop', CheckBoxInputView); + } + } + }); + } + ); + const widgets = new WidgetsCollection([ + {type_name: 'iconInlineFileRights', role: 'inlineFileRights'}, + ], {widgetTypes}); + const groups = new ConfigurationEditorTabView.Groups(); + widgets.subject = factories.entry(); + + widgets.setupConfigurationEditorTabViewGroups(groups); + const context = { + input: jest.fn() + }; + + expect(() => { + groups.apply( + 'ContentElementInlineFileRightsSettings', + context + ); + }).not.toThrowError(); + + expect(context.input).not.toHaveBeenCalled(); + }); + + it('redefines configuration editor tab view groups on type change', () => { + const widgetTypes = factories.widgetTypes( + [ + { + role: 'inlineFileRights', + name: 'iconInlineFileRights', + insertPoint: 'react' + }, + { + role: 'inlineFileRights', + name: 'textInlineFileRights', + insertPoint: 'react' + } + ], + w => { + w.register('textInlineFileRights', { + configurationEditorTabViewGroups: { + ContentElementInlineFileRightsSettings() { + this.input('showBackdrop', CheckBoxInputView); + } + } + }); + } + ); + const widgets = new WidgetsCollection([ + {type_name: 'iconInlineFileRights', role: 'inlineFileRights'}, + ], {widgetTypes}); + const groups = new ConfigurationEditorTabView.Groups(); + widgets.subject = factories.entry(); + + widgets.setupConfigurationEditorTabViewGroups(groups); + widgets.first().set('type_name', 'textInlineFileRights'); + const context = { + input: jest.fn() + }; + groups.apply( + 'ContentElementInlineFileRightsSettings', + context + ); + + expect(context.input).toHaveBeenCalled(); + }); }); diff --git a/package/src/editor/api/WidgetType.js b/package/src/editor/api/WidgetType.js index fbf90ba388..ed6cc6d439 100644 --- a/package/src/editor/api/WidgetType.js +++ b/package/src/editor/api/WidgetType.js @@ -1,5 +1,4 @@ import _ from 'underscore'; - import {Object} from 'pageflow/ui'; export const WidgetType = Object.extend({ @@ -8,6 +7,7 @@ export const WidgetType = Object.extend({ this.translationKey = serverSideConfig.translationKey; this.insertPoint = serverSideConfig.insertPoint; this.configurationEditorView = clientSideConfig.configurationEditorView; + this.configurationEditorTabViewGroups = clientSideConfig.configurationEditorTabViewGroups || {}; this.isOptional = clientSideConfig.isOptional; }, @@ -23,5 +23,17 @@ export const WidgetType = Object.extend({ 'pageflow.editor.widgets.common_attributes' ] }, options)); + }, + + defineStubConfigurationEditorTabViewGroups(groups) { + _.each(this.configurationEditorTabViewGroups, (fn, name) => + groups.define(name, () => {}) + ); + }, + + defineConfigurationEditorTabViewGroups(groups) { + _.each(this.configurationEditorTabViewGroups, (fn, name) => + groups.define(name, fn) + ); } }); diff --git a/package/src/editor/api/WidgetTypes.js b/package/src/editor/api/WidgetTypes.js index 392c2e8cb1..b438772fb2 100644 --- a/package/src/editor/api/WidgetTypes.js +++ b/package/src/editor/api/WidgetTypes.js @@ -55,5 +55,11 @@ export const WidgetTypes = Object.extend({ isOptional: function(role) { return !!this._optionalRoles[role]; + }, + + defineStubConfigurationEditorTabViewGroups(groups) { + _.each(this._widgetTypesByName, widgetType => + widgetType.defineStubConfigurationEditorTabViewGroups(groups) + ); } }); diff --git a/package/src/editor/collections/WidgetsCollection.js b/package/src/editor/collections/WidgetsCollection.js index c5375a6832..c55db289a4 100644 --- a/package/src/editor/collections/WidgetsCollection.js +++ b/package/src/editor/collections/WidgetsCollection.js @@ -7,7 +7,9 @@ import {SubsetCollection} from './SubsetCollection'; export const WidgetsCollection = Backbone.Collection.extend({ model: Widget, - initialize: function() { + initialize: function(widgets, options) { + this.widgetTypes = options.widgetTypes; + this.listenTo(this, 'change:type_name change:configuration', function() { this.batchSave(); }); @@ -35,6 +37,18 @@ export const WidgetsCollection = Backbone.Collection.extend({ })); }, + setupConfigurationEditorTabViewGroups(groups) { + this.defineConfigurationEditorTabViewGroups(groups); + this.listenTo(this, 'change:type_name', () => + this.defineConfigurationEditorTabViewGroups(groups) + ); + }, + + defineConfigurationEditorTabViewGroups(groups) { + this.widgetTypes.defineStubConfigurationEditorTabViewGroups(groups); + this.each(widget => widget.defineConfigurationEditorTabViewGroups(groups)); + }, + withInsertPoint(insertPoint) { return new SubsetCollection({ parent: this, diff --git a/package/src/editor/initializers/setupCollections.js b/package/src/editor/initializers/setupCollections.js index 9e54bbfe3b..47a63cc6f0 100644 --- a/package/src/editor/initializers/setupCollections.js +++ b/package/src/editor/initializers/setupCollections.js @@ -2,6 +2,7 @@ import Backbone from 'backbone'; import _ from 'underscore'; import {events} from 'pageflow/frontend'; +import {ConfigurationEditorTabView} from 'pageflow/ui'; import {ChaptersCollection} from '../collections/ChaptersCollection'; import {FilesCollection} from '../collections/FilesCollection'; @@ -38,6 +39,7 @@ app.addInitializer(function(options) { state.account = new Backbone.Model(options.account); widgets.subject = state.entry; + widgets.setupConfigurationEditorTabViewGroups(ConfigurationEditorTabView.groups); state.storylineOrdering = new StorylineOrdering(state.storylines, state.pages); state.storylineOrdering.sort({silent: true}); diff --git a/package/src/editor/models/Widget.js b/package/src/editor/models/Widget.js index ebc50d50a9..d66a4b8df3 100644 --- a/package/src/editor/models/Widget.js +++ b/package/src/editor/models/Widget.js @@ -23,6 +23,11 @@ export const Widget = Backbone.Model.extend({ return this.get('type_name') && this.widgetTypes.findByName(this.get('type_name')); }, + defineConfigurationEditorTabViewGroups(groups) { + this.widgetType() && + this.widgetType().defineConfigurationEditorTabViewGroups(groups); + }, + hasConfiguration: function() { return !!(this.widgetType() && this.widgetType().hasConfiguration()); }, From a4510afc17eec8ebeca69a0d1077724a49937a34 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 24 Oct 2024 10:23:58 +0200 Subject: [PATCH 3/5] Allow configuring figure caption variants via theme property scopes Use transient state to detect whether a caption is present and enable/disable select input accordingly. Generalize content element variant concept to component variants to allow configuring different variants of theme property scopes that do not correspond to content elements. REDMINE-20848 --- .../locales/new/caption_settings.de.yml | 9 ++ .../locales/new/caption_settings.en.yml | 9 ++ .../spec/editor/models/ScrolledEntry-spec.js | 129 +++++++++++++++++- .../frontend/ContentElementFigure-spec.js | 104 ++++++++++++++ .../package/spec/frontend/Figure-spec.js | 13 ++ .../dataWrapperChart/editor.js | 6 +- .../contentElements/hotspots/editor/index.js | 2 + .../src/contentElements/iframeEmbed/editor.js | 4 +- .../imageGallery/ImageGallery.js | 4 +- .../imageGallery/editor/index.js | 4 +- .../contentElements/imageGallery/stories.js | 71 ++++++---- .../src/contentElements/inlineAudio/editor.js | 4 + .../inlineBeforeAfter/editor.js | 8 +- .../src/contentElements/inlineImage/editor.js | 9 +- .../contentElements/inlineImage/stories.js | 17 +++ .../src/contentElements/inlineVideo/editor.js | 6 +- .../src/contentElements/videoEmbed/editor.js | 6 +- .../src/contentElements/vrImage/editor.js | 6 +- .../src/editor/models/ScrolledEntry/index.js | 15 +- .../groups/CommonContentElementAttributes.js | 24 ++++ .../src/frontend/ContentElementFigure.js | 17 ++- .../scrolled/package/src/frontend/Figure.js | 9 +- .../package/src/frontend/Figure.module.css | 10 +- 23 files changed, 431 insertions(+), 55 deletions(-) create mode 100644 entry_types/scrolled/config/locales/new/caption_settings.de.yml create mode 100644 entry_types/scrolled/config/locales/new/caption_settings.en.yml create mode 100644 entry_types/scrolled/package/spec/frontend/ContentElementFigure-spec.js diff --git a/entry_types/scrolled/config/locales/new/caption_settings.de.yml b/entry_types/scrolled/config/locales/new/caption_settings.de.yml new file mode 100644 index 0000000000..4c871784af --- /dev/null +++ b/entry_types/scrolled/config/locales/new/caption_settings.de.yml @@ -0,0 +1,9 @@ +de: + pageflow_scrolled: + editor: + common_content_element_attributes: + captionVariant: + label: Beschriftungsvariante + inline_help: Ändere die Darstellung der Beschriftung unterhalb des Element. + inline_help_disabled: Füge eine Beschriftung unterhalb des Elements hinzu, um zwischen Varianten zu wählen. + blank: "(Standard)" diff --git a/entry_types/scrolled/config/locales/new/caption_settings.en.yml b/entry_types/scrolled/config/locales/new/caption_settings.en.yml new file mode 100644 index 0000000000..4c5b1b54d1 --- /dev/null +++ b/entry_types/scrolled/config/locales/new/caption_settings.en.yml @@ -0,0 +1,9 @@ +en: + pageflow_scrolled: + editor: + common_content_element_attributes: + captionVariant: + label: Caption Variant + inline_help: Switch between different looks of the caption below the element. + inline_help_disabled: Add a caption below the element to switch between different looks. + blank: "(Default)" diff --git a/entry_types/scrolled/package/spec/editor/models/ScrolledEntry-spec.js b/entry_types/scrolled/package/spec/editor/models/ScrolledEntry-spec.js index 699e18f633..2524d041c2 100644 --- a/entry_types/scrolled/package/spec/editor/models/ScrolledEntry-spec.js +++ b/entry_types/scrolled/package/spec/editor/models/ScrolledEntry-spec.js @@ -2915,13 +2915,13 @@ describe('ScrolledEntry', () => { ); const contentElement = entry.contentElements.get(5); - const [values, translationKeys] = entry.getContentElementVariants({contentElement}); + const [values, texts] = entry.getContentElementVariants({contentElement}); expect(values).toEqual([]); - expect(translationKeys).toEqual([]); + expect(texts).toEqual([]); }); - it('selects typography rules based on content element type name', () => { + it('selects property scopes based on content element type name', () => { editor.contentElementTypes.register('someElement', {}); const entry = factories.entry( @@ -3046,6 +3046,129 @@ describe('ScrolledEntry', () => { }); }); + describe('getComponentVariants', () => { + it('returns empty arrays by default', () => { + const entry = factories.entry( + ScrolledEntry, + {}, + { + entryTypeSeed: normalizeSeed() + } + ); + + const [values, texts] = entry.getComponentVariants({name: 'figureCaption'}); + + expect(values).toEqual([]); + expect(texts).toEqual([]); + }); + + it('selects property scopes based on name', () => { + const entry = factories.entry( + ScrolledEntry, + {}, + { + entryTypeSeed: normalizeSeed({ + themeOptions: { + properties: { + 'figureCaption-blue': { + surface_color: 'blue' + }, + 'figureCaption-green': { + surface_color: 'green' + } + } + } + }) + } + ); + + const [values] = entry.getComponentVariants({name: 'figureCaption'}); + + expect(values).toEqual(['blue', 'green']); + }); + + describe('with shared translations', () => { + const commonPrefix = 'pageflow_scrolled.editor.component_variants' + + useFakeTranslations({ + [`${commonPrefix}.figureCaption-blue`]: 'Blue', + [`${commonPrefix}.figureCaption-green`]: 'Green' + }); + + it('returns translated display names', () => { + const entry = factories.entry( + ScrolledEntry, + { + metadata: {theme_name: 'custom'} + }, + { + entryTypeSeed: normalizeSeed({ + themeOptions: { + properties: { + 'figureCaption-blue': { + surface_color: 'blue' + }, + 'figureCaption-green': { + surface_color: 'green' + } + } + } + }) + } + ); + + const [, texts] = entry.getComponentVariants({name: 'figureCaption'}); + + expect(texts).toEqual([ + 'Blue', + 'Green' + ]); + }); + }); + + describe('with theme specific translations', () => { + const commonPrefix = 'pageflow_scrolled.editor.component_variants' + const themePrefix = `pageflow_scrolled.editor.themes.custom` + + useFakeTranslations({ + [`${commonPrefix}.figureCaption-blue`]: 'Blue', + [`${commonPrefix}.figureCaption-green`]: 'Green', + [`${themePrefix}.component_variants.figureCaption-blue`]: 'Custom Blue', + [`${themePrefix}.component_variants.figureCaption-green`]: 'Custom Green' + }); + + it('prefers theme specific translations', () => { + const entry = factories.entry( + ScrolledEntry, + { + metadata: {theme_name: 'custom'} + }, + { + entryTypeSeed: normalizeSeed({ + themeOptions: { + properties: { + 'figureCaption-blue': { + surface_color: 'blue' + }, + 'figureCaption-green': { + surface_color: 'green' + } + } + } + }) + } + ); + + const [, texts] = entry.getComponentVariants({name: 'figureCaption'}); + + expect(texts).toEqual([ + 'Custom Blue', + 'Custom Green' + ]); + }); + }); + }); + describe('supportsSectionWidths', () => { it('returns false by default', () => { const entry = factories.entry( diff --git a/entry_types/scrolled/package/spec/frontend/ContentElementFigure-spec.js b/entry_types/scrolled/package/spec/frontend/ContentElementFigure-spec.js new file mode 100644 index 0000000000..2b2c68c62a --- /dev/null +++ b/entry_types/scrolled/package/spec/frontend/ContentElementFigure-spec.js @@ -0,0 +1,104 @@ +import React from 'react'; +import '@testing-library/jest-dom/extend-expect' + +import {renderInContentElement} from 'support'; + +import {ContentElementFigure} from 'frontend/ContentElementFigure'; + +describe('ContentElementFigure', () => { + it('just renders children by default', () => { + const {queryByTestId} = + renderInContentElement( +
, + { + seed: {} + } + ); + + expect(queryByTestId('content')).not.toBeNull(); + }); + + it('applies variant scope', () => { + const configuration = { + caption: [{ + type: 'paragraph', + children: [{ text: 'Some caption text' }], + }], + captionVariant: 'invert' + }; + + const {container} = + renderInContentElement( + , + { + seed: {} + } + ); + + expect(container.querySelector('figcaption')).toHaveClass('scope-figureCaption-invert'); + }); + + it('sets hasCaption to true in transient state on mount', () => { + const configuration = { + caption: [{ + type: 'paragraph', + children: [{ text: 'Some caption text' }], + }], + captionVariant: 'invert' + }; + const setTransientState = jest.fn(); + + renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: true, setTransientState} + } + ); + + expect(setTransientState).toHaveBeenCalledWith({hasCaption: true}) + }); + + it('sets hasCaption to false in transient state on unmount', () => { + const configuration = { + caption: [{ + type: 'paragraph', + children: [{ text: 'Some caption text' }], + }], + captionVariant: 'invert' + }; + const setTransientState = jest.fn(); + + const {unmount} = renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: true, setTransientState} + } + ); + unmount(); + + expect(setTransientState).toHaveBeenCalledWith({hasCaption: false}) + }); + + it('does not render transient state component outside of editor', () => { + const configuration = { + caption: [{ + type: 'paragraph', + children: [{ text: 'Some caption text' }], + }], + captionVariant: 'invert' + }; + const setTransientState = jest.fn(); + + renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: false, setTransientState} + } + ); + + expect(setTransientState).not.toHaveBeenCalled(); + }); +}); diff --git a/entry_types/scrolled/package/spec/frontend/Figure-spec.js b/entry_types/scrolled/package/spec/frontend/Figure-spec.js index e40ac07a52..7ba41a6412 100644 --- a/entry_types/scrolled/package/spec/frontend/Figure-spec.js +++ b/entry_types/scrolled/package/spec/frontend/Figure-spec.js @@ -39,4 +39,17 @@ describe('Figure', () => { expect(queryByRole('figure')).toHaveTextContent('Some caption text'); expect(queryByTestId('content')).not.toBeNull(); }); + + it('applies variant scope', () => { + const value = [{ + type: 'paragraph', + children: [{ text: 'Some caption text' }], + }]; + const {container} = + renderInEntry(
, { + seed: {} + }); + + expect(container.querySelector('figcaption')).toHaveClass('scope-figureCaption-invert'); + }); }); diff --git a/entry_types/scrolled/package/src/contentElements/dataWrapperChart/editor.js b/entry_types/scrolled/package/src/contentElements/dataWrapperChart/editor.js index 7dd9b21e92..fbe435a9a9 100644 --- a/entry_types/scrolled/package/src/contentElements/dataWrapperChart/editor.js +++ b/entry_types/scrolled/package/src/contentElements/dataWrapperChart/editor.js @@ -1,6 +1,6 @@ import I18n from 'i18n-js'; import {editor} from 'pageflow-scrolled/editor'; -import {UrlInputView, TextInputView, ColorInputView} from 'pageflow/ui'; +import {SeparatorView, UrlInputView, TextInputView, ColorInputView} from 'pageflow/ui'; import {DatawrapperAdView} from './editor/DataWrapperAdView'; import pictogram from './pictogram.svg'; @@ -11,7 +11,7 @@ editor.contentElementTypes.register('dataWrapperChart', { supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right'], supportedWidthRange: ['xxs', 'full'], - configurationEditor() { + configurationEditor({entry}) { this.tab('general', function() { this.input('url', UrlInputView, { supportedHosts: [ @@ -33,6 +33,8 @@ editor.contentElementTypes.register('dataWrapperChart', { }); this.group('ContentElementPosition'); + this.view(SeparatorView); + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js index 0103cce095..25a002be5b 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js @@ -59,6 +59,8 @@ editor.contentElementTypes.register('hotspots', { displayUncheckedIfDisabled: true }); this.group('ContentElementPosition'); + this.view(SeparatorView); + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/iframeEmbed/editor.js b/entry_types/scrolled/package/src/contentElements/iframeEmbed/editor.js index 6679e3a28c..03ab6ce83b 100644 --- a/entry_types/scrolled/package/src/contentElements/iframeEmbed/editor.js +++ b/entry_types/scrolled/package/src/contentElements/iframeEmbed/editor.js @@ -1,7 +1,7 @@ import I18n from 'i18n-js'; import {editor} from 'pageflow-scrolled/editor'; import {InfoBoxView} from 'pageflow/editor'; -import {TextInputView, SelectInputView, CheckBoxInputView} from 'pageflow/ui'; +import {TextInputView, SelectInputView, CheckBoxInputView, SeparatorView} from 'pageflow/ui'; import pictogram from './pictogram.svg'; @@ -42,6 +42,8 @@ editor.contentElementTypes.register('iframeEmbed', { values: ['p100', 'p75', 'p50', 'p33'] }); this.group('ContentElementPosition'); + this.view(SeparatorView); + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js b/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js index fb6f7dbb4a..9b9597e42b 100644 --- a/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js +++ b/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js @@ -159,6 +159,7 @@ function Scroller({ item={item} current={index === visibleIndex} captions={configuration.captions || {}} + captionVariant={configuration.captionVariant} onClick={handleClick}> {displayFullscreenToggle &&
diff --git a/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js b/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js index 8b8a3f064f..f4136f04a2 100644 --- a/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js +++ b/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js @@ -1,6 +1,6 @@ import {editor} from 'pageflow-scrolled/editor'; import {contentElementWidths} from 'pageflow-scrolled/frontend'; -import {CheckBoxInputView} from 'pageflow/editor'; +import {CheckBoxInputView, SeparatorView} from 'pageflow/editor'; import {ItemsListView} from './ItemsListView'; import {ItemsCollection} from './models/ItemsCollection'; @@ -24,6 +24,8 @@ editor.contentElementTypes.register('imageGallery', { displayUncheckedIfDisabled: true }); this.group('ContentElementPosition'); + this.view(SeparatorView); + this.group('ContentElementCaption', {entry, disableWhenNoCaption: false}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/imageGallery/stories.js b/entry_types/scrolled/package/src/contentElements/imageGallery/stories.js index 7a4943cc99..ab0259fdc3 100644 --- a/entry_types/scrolled/package/src/contentElements/imageGallery/stories.js +++ b/entry_types/scrolled/package/src/contentElements/imageGallery/stories.js @@ -7,39 +7,15 @@ storiesOfContentElement(module, { items: [ { id: 1, - image: filePermaId('imageFiles', 'turtle'), - caption: [ - { - type: 'paragraph', - children: [ - {text: 'At vero eos et accusam et justo duo dolores et ea rebum.'} - ] - } - ] + image: filePermaId('imageFiles', 'turtle') }, { id: 2, - image: filePermaId('imageFiles', 'churchBefore'), - caption: [ - { - type: 'paragraph', - children: [ - {text: 'At vero eos et accusam et justo duo dolores et ea rebum.'} - ] - } - ] + image: filePermaId('imageFiles', 'churchBefore') }, { id: 3, - image: filePermaId('imageFiles', 'churchAfter'), - caption: [ - { - type: 'paragraph', - children: [ - {text: 'At vero eos et accusam et justo duo dolores et ea rebum.'} - ] - } - ] + image: filePermaId('imageFiles', 'churchAfter') }, { id: 4, @@ -66,6 +42,47 @@ storiesOfContentElement(module, { } } } + }, + { + name: 'With Captions', + configuration: { + captions: { + 1: [ + { + type: 'paragraph', + children: [ + {text: 'At vero eos et accusam et justo duo dolores et ea rebum.'} + ] + } + ] + } + } + }, + { + name: 'With Caption Variant', + configuration: { + captions: { + 1: [ + { + type: 'paragraph', + children: [ + {text: 'At vero eos et accusam et justo duo dolores et ea rebum.'} + ] + } + ] + }, + captionVariant: 'inverted' + }, + themeOptions: { + properties: { + 'figureCaption-inverted': { + darkContentSurfaceColor: 'var(--root-light-content-surface-color)', + lightContentSurfaceColor: 'var(--root-dark-content-surface-color)', + darkContentTextColor: 'var(--root-light-content-text-color)', + lightContentTextColor: 'var(--root-dark-content-text-color)' + } + } + } } ] }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js b/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js index 33edfd026a..e529a36a5a 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js @@ -53,6 +53,10 @@ editor.contentElementTypes.register('inlineAudio', { this.view(SeparatorView); this.group('ContentElementPosition'); + + this.view(SeparatorView); + + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js index 322e95dffd..177eddc9a6 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js @@ -1,6 +1,6 @@ import {editor, InlineFileRightsMenuItem} from 'pageflow-scrolled/editor'; import {ColorInputView, FileInputView} from 'pageflow/editor'; -import {SliderInputView, TextInputView} from 'pageflow/ui'; +import {SliderInputView, TextInputView, SeparatorView} from 'pageflow/ui'; import pictogram from './pictogram.svg'; @@ -10,7 +10,7 @@ editor.contentElementTypes.register('inlineBeforeAfter', { supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right', 'backdrop'], supportedWidthRange: ['xxs', 'full'], - configurationEditor() { + configurationEditor({entry}) { this.tab('general', function() { this.input('before_id', FileInputView, { collection: 'image_files', @@ -30,6 +30,10 @@ editor.contentElementTypes.register('inlineBeforeAfter', { this.input('slider_color', ColorInputView); this.group('ContentElementPosition'); + + this.view(SeparatorView); + + this.group('ContentElementCaption', {entry}); }); }, diff --git a/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js b/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js index 70bdf3f990..47d9bb7e59 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js @@ -1,6 +1,7 @@ import {editor, InlineFileRightsMenuItem} from 'pageflow-scrolled/editor'; import {contentElementWidths} from 'pageflow-scrolled/frontend'; -import {FileInputView, CheckBoxInputView} from 'pageflow/editor'; +import {FileInputView} from 'pageflow/editor'; +import {SeparatorView, CheckBoxInputView} from 'pageflow/ui'; import pictogram from './pictogram.svg'; @@ -10,7 +11,7 @@ editor.contentElementTypes.register('inlineImage', { supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right'], supportedWidthRange: ['xxs', 'full'], - configurationEditor({contentElement}) { + configurationEditor({entry, contentElement}) { this.tab('general', function() { this.input('id', FileInputView, { collection: 'image_files', @@ -29,6 +30,10 @@ editor.contentElementTypes.register('inlineImage', { displayUncheckedIfDisabled: true }); this.group('ContentElementPosition'); + + this.view(SeparatorView); + + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineImage/stories.js b/entry_types/scrolled/package/src/contentElements/inlineImage/stories.js index 369f127f96..3419748e4a 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineImage/stories.js +++ b/entry_types/scrolled/package/src/contentElements/inlineImage/stories.js @@ -10,6 +10,23 @@ storiesOfContentElement(module, { { name: 'With Caption', configuration: {caption: 'Some text here'} + }, + { + name: 'With Caption Variant', + configuration: { + caption: 'Some text here', + captionVariant: 'inverted' + }, + themeOptions: { + properties: { + 'figureCaption-inverted': { + darkContentSurfaceColor: 'var(--root-light-content-surface-color)', + lightContentSurfaceColor: 'var(--root-dark-content-surface-color)', + darkContentTextColor: 'var(--root-light-content-text-color)', + lightContentTextColor: 'var(--root-dark-content-text-color)' + } + } + } } ], inlineFileRights: true diff --git a/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js b/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js index 34de4495cb..70b37b67fb 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js @@ -38,7 +38,7 @@ editor.contentElementTypes.register('inlineVideo', { supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right', 'backdrop'], supportedWidthRange: ['xxs', 'full'], - configurationEditor() { + configurationEditor({entry}) { migrateLegacyAutoplay(this.model); this.tab('general', function() { @@ -104,6 +104,10 @@ editor.contentElementTypes.register('inlineVideo', { this.view(SeparatorView); this.group('ContentElementPosition'); + + this.view(SeparatorView); + + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/videoEmbed/editor.js b/entry_types/scrolled/package/src/contentElements/videoEmbed/editor.js index 4ccfeff597..4cc8bf3c48 100644 --- a/entry_types/scrolled/package/src/contentElements/videoEmbed/editor.js +++ b/entry_types/scrolled/package/src/contentElements/videoEmbed/editor.js @@ -10,7 +10,7 @@ editor.contentElementTypes.register('videoEmbed', { supportedPositions: ['inline', 'sticky', 'standAlone', 'left', 'right'], supportedWidthRange: ['xxs', 'full'], - configurationEditor() { + configurationEditor({entry}) { this.tab('general', function() { this.input('videoSource', UrlInputView, { supportedHosts: [ @@ -47,6 +47,10 @@ editor.contentElementTypes.register('videoEmbed', { this.view(SeparatorView); this.group('ContentElementPosition'); + + this.view(SeparatorView); + + this.group('ContentElementCaption', {entry}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/vrImage/editor.js b/entry_types/scrolled/package/src/contentElements/vrImage/editor.js index 9b24ca962c..c56b79f79f 100644 --- a/entry_types/scrolled/package/src/contentElements/vrImage/editor.js +++ b/entry_types/scrolled/package/src/contentElements/vrImage/editor.js @@ -1,5 +1,5 @@ import {editor, InlineFileRightsMenuItem} from 'pageflow-scrolled/editor'; -import {SelectInputView, FileInputView, EnumTableCellView, SliderInputView} from 'pageflow/editor'; +import {SelectInputView, FileInputView, EnumTableCellView, SliderInputView, SeparatorView} from 'pageflow/editor'; import pictogram from './pictogram.svg'; @@ -10,7 +10,7 @@ editor.contentElementTypes.register('vrImage', { supportedWidthRange: ['xxs', 'full'], configurationEditor() { - this.tab('general', function() { + this.tab('general', function({entry}) { this.input('image', FileInputView, { collection: 'image_files', fileSelectionHandler: 'contentElementConfiguration', @@ -29,6 +29,8 @@ editor.contentElementTypes.register('vrImage', { maxValue: 60 }); this.group('ContentElementPosition'); + this.view(SeparatorView); + this.group('ContentElementCaption', {entry}); }); } }); 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 8d34704670..958c15326b 100644 --- a/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js +++ b/entry_types/scrolled/package/src/editor/models/ScrolledEntry/index.js @@ -177,23 +177,30 @@ export const ScrolledEntry = Entry.extend({ }, getContentElementVariants({contentElement}) { + return this.getComponentVariants({ + name: contentElement.get('typeName'), + translationKeysScope: 'content_element_variants' + }); + }, + + getComponentVariants({name, translationKeysScope = 'component_variants'}) { const scopeNames = Object.keys( this.scrolledSeed.config.theme.options.properties || {} ); - const scopeNamePrefix = `${contentElement.get('typeName')}-`; - + const scopeNamePrefix = `${name}-`; const matchingScopeNames = scopeNames.filter( name => name.indexOf(scopeNamePrefix) === 0 ); + const values = matchingScopeNames.map( name => name.replace(scopeNamePrefix, '') ); const texts = matchingScopeNames.map(name => I18n.t( `pageflow_scrolled.editor.themes.${this.metadata.get('theme_name')}` + - `.content_element_variants.${name}`, - {defaultValue: I18n.t(`pageflow_scrolled.editor.content_element_variants.${name}`)} + `.${translationKeysScope}.${name}`, + {defaultValue: I18n.t(`pageflow_scrolled.editor.${translationKeysScope}.${name}`)} ) ); diff --git a/entry_types/scrolled/package/src/editor/views/configurationEditors/groups/CommonContentElementAttributes.js b/entry_types/scrolled/package/src/editor/views/configurationEditors/groups/CommonContentElementAttributes.js index fa522f038f..3beaa14d22 100644 --- a/entry_types/scrolled/package/src/editor/views/configurationEditors/groups/CommonContentElementAttributes.js +++ b/entry_types/scrolled/package/src/editor/views/configurationEditors/groups/CommonContentElementAttributes.js @@ -117,3 +117,27 @@ ConfigurationEditorTabView.groups.define( } } ); + +ConfigurationEditorTabView.groups.define( + 'ContentElementCaption', + function({entry, disableWhenNoCaption = true}) { + const [variants, texts] = entry.getComponentVariants({ + name: 'figureCaption' + }); + + this.input('captionVariant', SelectInputView, { + attributeTranslationKeyPrefixes: [ + 'pageflow_scrolled.editor.common_content_element_attributes' + ], + includeBlank: true, + blankTranslationKey: 'pageflow_scrolled.editor.' + + 'common_content_element_attributes.' + + 'captionVariant.blank', + values: variants, + texts, + disabledBindingModel: this.model.parent.transientState, + disabledBinding: 'hasCaption', + disabled: hasCaption => disableWhenNoCaption && !hasCaption + }); + } +); diff --git a/entry_types/scrolled/package/src/frontend/ContentElementFigure.js b/entry_types/scrolled/package/src/frontend/ContentElementFigure.js index 6b96213b47..d3b4a7fbc3 100644 --- a/entry_types/scrolled/package/src/frontend/ContentElementFigure.js +++ b/entry_types/scrolled/package/src/frontend/ContentElementFigure.js @@ -1,8 +1,9 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {Figure} from './Figure'; import {useContentElementConfigurationUpdate} from './useContentElementConfigurationUpdate'; import {useContentElementAttributes} from './useContentElementAttributes'; +import {useContentElementEditorState} from './useContentElementEditorState'; import {widths} from './layouts'; /** @@ -13,6 +14,7 @@ import {widths} from './layouts'; export function ContentElementFigure({configuration, children}) { const updateConfiguration = useContentElementConfigurationUpdate(); const {width, position} = useContentElementAttributes(); + const {isEditable} = useContentElementEditorState(); if (position === 'backdrop') { return children; @@ -20,9 +22,22 @@ export function ContentElementFigure({configuration, children}) { return (
isEditable && } onCaptionChange={caption => updateConfiguration({caption})} addCaptionButtonPosition={width === widths.full ? 'outsideIndented' : 'outside'}> {children}
); } + +function HasCaptionTransientState() { + const {setTransientState} = useContentElementEditorState(); + + useEffect(() => { + setTransientState({hasCaption: true}); + return () => setTransientState({hasCaption: false}); + }, [setTransientState]); + + return null; +} diff --git a/entry_types/scrolled/package/src/frontend/Figure.js b/entry_types/scrolled/package/src/frontend/Figure.js index 5f66132f92..aaa30bebfc 100644 --- a/entry_types/scrolled/package/src/frontend/Figure.js +++ b/entry_types/scrolled/package/src/frontend/Figure.js @@ -16,14 +16,17 @@ import styles from './Figure.module.css'; * @param {Object} props * @param {string} props.children - Content of figure. * @param {Object[]|string} props.caption - Formatted text data as provided by onCaptionChange. + * @param {string} [props.variant] - Name of figureCaption property scope to apply. * @param {Function} props.onCaptionChange - Receives updated value when it changes. * @param {boolean} [props.addCaptionButtonVisible=true] - Control visiblility of action button. * @param {string} [props.captionButtonPosition='outside'] - Position of action button. */ export function Figure({ children, + variant, caption, onCaptionChange, - addCaptionButtonVisible = true, addCaptionButtonPosition = 'outside' + addCaptionButtonVisible = true, addCaptionButtonPosition = 'outside', + renderInsideCaption }) { const darkBackground = useDarkBackground(); const {isSelected, isEditable} = useContentElementEditorState(); @@ -49,7 +52,9 @@ export function Figure({ onClick={() => setIsEditingCaption(true)} />} {(!isBlankEditableTextValue(caption) || isEditingCaption) && -
setIsEditingCaption(false)}> +
setIsEditingCaption(false)}> + {renderInsideCaption?.()} figcaption { - padding: 3px 10px 5px; - background-color: lightContentSurfaceColor; - color: darkContentTextColor; + padding: 3px var(--theme-figure-caption-padding-inline, 10px) 5px; + background-color: var(--theme-figure-caption-surface-color, lightContentSurfaceColor); + color: var(--theme-figure-caption-text-color, darkContentTextColor); } .root > figcaption p { @@ -20,6 +20,6 @@ } .invert > figcaption { - background-color: darkContentSurfaceColor; - color: lightContentTextColor; + background-color: var(--theme-figure-caption-surface-color, darkContentSurfaceColor); + color: var(--theme-figure-caption-text-color, lightContentTextColor); } From b72cd278036bc8ac07b85932d328c2858d0725ce Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 24 Oct 2024 12:21:55 +0200 Subject: [PATCH 4/5] Allow displaying a backdrop behind text inline file rights Enable checkbox only when inline file rights are displayed. REDMINE-20848 --- .../locales/new/caption_settings.de.yml | 5 ++ .../locales/new/caption_settings.en.yml | 5 ++ .../spec/frontend/InlineFileRights-spec.js | 11 +++- .../TextInlineFileRights-spec.js | 66 +++++++++++++++++++ .../src/contentElements/hotspots/Hotspots.js | 8 ++- .../contentElements/hotspots/editor/index.js | 1 + .../imageGallery/ImageGallery.js | 16 +++-- .../imageGallery/editor/index.js | 1 + .../inlineAudio/InlineAudio.js | 4 +- .../src/contentElements/inlineAudio/editor.js | 1 + .../inlineBeforeAfter/BeforeAfter.js | 8 ++- .../inlineBeforeAfter/editor.js | 1 + .../inlineImage/InlineImage.js | 8 ++- .../src/contentElements/inlineImage/editor.js | 1 + .../inlineVideo/InlineVideo.js | 4 +- .../src/contentElements/inlineVideo/editor.js | 1 + .../src/contentElements/vrImage/VrImage.js | 8 ++- .../src/contentElements/vrImage/editor.js | 1 + .../scrolled/package/src/editor/config.js | 16 +++++ .../package/src/frontend/InlineFileRights.js | 5 +- .../TextInlineFileRights.js | 24 +++++-- .../TextInlineFileRights.module.css | 22 ++++--- 22 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 entry_types/scrolled/package/spec/widgets/textInlineFileRights/TextInlineFileRights-spec.js diff --git a/entry_types/scrolled/config/locales/new/caption_settings.de.yml b/entry_types/scrolled/config/locales/new/caption_settings.de.yml index 4c871784af..f833467147 100644 --- a/entry_types/scrolled/config/locales/new/caption_settings.de.yml +++ b/entry_types/scrolled/config/locales/new/caption_settings.de.yml @@ -1,6 +1,11 @@ de: pageflow_scrolled: editor: + content_element_text_inline_file_rights_attributes: + showTextInlineFileRightsBackdrop: + label: "Abblendung hinter Rechteangabe" + inline_help: "Lesbarkeit des Texts auf unruhigen Hintergründen sicherstellen." + inline_help_disabled: "Steht nur zur Verfügung, wenn Rechteangaben am Element angezeigt werden." common_content_element_attributes: captionVariant: label: Beschriftungsvariante diff --git a/entry_types/scrolled/config/locales/new/caption_settings.en.yml b/entry_types/scrolled/config/locales/new/caption_settings.en.yml index 4c5b1b54d1..d69604f15d 100644 --- a/entry_types/scrolled/config/locales/new/caption_settings.en.yml +++ b/entry_types/scrolled/config/locales/new/caption_settings.en.yml @@ -1,6 +1,11 @@ en: pageflow_scrolled: editor: + content_element_text_inline_file_rights_attributes: + showTextInlineFileRightsBackdrop: + label: "Backdrop behind inline file rights" + inline_help: "Improve readability on busy backgrounds." + inline_help_disabled: "Only available when rights texts are displayed at the element." common_content_element_attributes: captionVariant: label: Caption Variant diff --git a/entry_types/scrolled/package/spec/frontend/InlineFileRights-spec.js b/entry_types/scrolled/package/spec/frontend/InlineFileRights-spec.js index 7cd080a04a..fce8b7c85a 100644 --- a/entry_types/scrolled/package/spec/frontend/InlineFileRights-spec.js +++ b/entry_types/scrolled/package/spec/frontend/InlineFileRights-spec.js @@ -45,10 +45,14 @@ describe('InlineFileRights', () => { it('passes props to widget', () => { api.widgetTypes.register('inlineFileRightsWithProps', { - component: function ({children, context, playerControlsFadedOut, playerControlsStandAlone}) { + component: function ({ + children, + context, playerControlsFadedOut, playerControlsStandAlone, + configuration + }) { return (
- {context} {playerControlsFadedOut.toString()} {playerControlsStandAlone.toString()} + {context} {playerControlsFadedOut.toString()} {playerControlsStandAlone.toString()} {configuration.some}
) } @@ -64,6 +68,7 @@ describe('InlineFileRights', () => { const {container} = renderInEntry( , { @@ -73,7 +78,7 @@ describe('InlineFileRights', () => { } ); - expect(container).toHaveTextContent('playerControls false true'); + expect(container).toHaveTextContent('playerControls false true customOption'); }); it('renders items for rights', () => { diff --git a/entry_types/scrolled/package/spec/widgets/textInlineFileRights/TextInlineFileRights-spec.js b/entry_types/scrolled/package/spec/widgets/textInlineFileRights/TextInlineFileRights-spec.js new file mode 100644 index 0000000000..585455c8ea --- /dev/null +++ b/entry_types/scrolled/package/spec/widgets/textInlineFileRights/TextInlineFileRights-spec.js @@ -0,0 +1,66 @@ +import React from 'react'; +import '@testing-library/jest-dom/extend-expect' + +import {renderInContentElement} from 'support'; + +import {TextInlineFileRights} from 'widgets/textInlineFileRights/TextInlineFileRights'; + +describe('TextInlineFileRights', () => { + it('renders children', () => { + const {queryByTestId} = + renderInContentElement( + +
+ , + { + seed: {} + } + ); + + expect(queryByTestId('content')).not.toBeNull(); + }); + + it('sets hasFileRights to true in transient state on mount', () => { + const setTransientState = jest.fn(); + + renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: true, setTransientState} + } + ); + + expect(setTransientState).toHaveBeenCalledWith({hasFileRights: true}) + }); + + it('sets hasFileRights to false in transient state on unmount', () => { + const setTransientState = jest.fn(); + + const {unmount} = renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: true, setTransientState} + } + ); + unmount(); + + expect(setTransientState).toHaveBeenCalledWith({hasFileRights: false}) + }); + + it('does not set transient state if context unsupported', () => { + const setTransientState = jest.fn(); + + const {unmount} = renderInContentElement( + , + { + seed: {}, + editorState: {isEditable: true, setTransientState} + } + ); + unmount(); + + expect(setTransientState).not.toHaveBeenCalled() + }); +}); diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/Hotspots.js b/entry_types/scrolled/package/src/contentElements/hotspots/Hotspots.js index 8acab44095..1fc3a6c75e 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/Hotspots.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/Hotspots.js @@ -293,7 +293,9 @@ export function HotspotsImage({ {renderIndicators()}
{renderFullscreenToggle()} - + )} } /> @@ -301,7 +303,9 @@ export function HotspotsImage({ } />
- + diff --git a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js index 25a002be5b..3d4b15e6e9 100644 --- a/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js +++ b/entry_types/scrolled/package/src/contentElements/hotspots/editor/index.js @@ -61,6 +61,7 @@ editor.contentElementTypes.register('hotspots', { this.group('ContentElementPosition'); this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js b/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js index 9b9597e42b..d894ca53ec 100644 --- a/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js +++ b/entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js @@ -158,8 +158,7 @@ function Scroller({ ref={setChildRef(index)} item={item} current={index === visibleIndex} - captions={configuration.captions || {}} - captionVariant={configuration.captionVariant} + configuration={configuration} onClick={handleClick}> {displayFullscreenToggle &&
@@ -212,11 +212,15 @@ const Item = forwardRef(function({item, captions, captionVariant, current, onCli {children} - +
- + diff --git a/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js b/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js index f4136f04a2..8776c6cc1c 100644 --- a/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js +++ b/entry_types/scrolled/package/src/contentElements/imageGallery/editor/index.js @@ -26,6 +26,7 @@ editor.contentElementTypes.register('imageGallery', { this.group('ContentElementPosition'); this.view(SeparatorView); this.group('ContentElementCaption', {entry, disableWhenNoCaption: false}); + this.group('ContentElementInlineFileRightsSettings', {entry, disableWhenNoFileRights: false}); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineAudio/InlineAudio.js b/entry_types/scrolled/package/src/contentElements/inlineAudio/InlineAudio.js index 7ce4b14f95..af81698642 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineAudio/InlineAudio.js +++ b/entry_types/scrolled/package/src/contentElements/inlineAudio/InlineAudio.js @@ -103,7 +103,9 @@ export function InlineAudio({contentElementId, configuration}) {
- + ) } diff --git a/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js b/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js index e529a36a5a..83d4266f3b 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineAudio/editor.js @@ -57,6 +57,7 @@ editor.contentElementTypes.register('inlineAudio', { this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/BeforeAfter.js b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/BeforeAfter.js index 22f14c3596..06013ee001 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/BeforeAfter.js +++ b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/BeforeAfter.js @@ -86,11 +86,15 @@ export function BeforeAfter(configuration) { ); }} - + - + ); diff --git a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js index 177eddc9a6..e6b8a84e67 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineBeforeAfter/editor.js @@ -34,6 +34,7 @@ editor.contentElementTypes.register('inlineBeforeAfter', { this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); }, diff --git a/entry_types/scrolled/package/src/contentElements/inlineImage/InlineImage.js b/entry_types/scrolled/package/src/contentElements/inlineImage/InlineImage.js index fd6a765864..d549371c7b 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineImage/InlineImage.js +++ b/entry_types/scrolled/package/src/contentElements/inlineImage/InlineImage.js @@ -82,11 +82,15 @@ function ImageWithCaption({imageFile, contentElementId, contentElementWidth, con 'large' : 'medium'} preferSvg={true} /> - + - + ); } diff --git a/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js b/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js index 47d9bb7e59..09b9069dcf 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineImage/editor.js @@ -34,6 +34,7 @@ editor.contentElementTypes.register('inlineImage', { this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/inlineVideo/InlineVideo.js b/entry_types/scrolled/package/src/contentElements/inlineVideo/InlineVideo.js index 92d0f7ef5d..2a956218b9 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineVideo/InlineVideo.js +++ b/entry_types/scrolled/package/src/contentElements/inlineVideo/InlineVideo.js @@ -146,7 +146,9 @@ function OrientationUnawareInlineVideo({ - + ) diff --git a/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js b/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js index 70b37b67fb..d778f9dc54 100644 --- a/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js +++ b/entry_types/scrolled/package/src/contentElements/inlineVideo/editor.js @@ -108,6 +108,7 @@ editor.contentElementTypes.register('inlineVideo', { this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); } }); diff --git a/entry_types/scrolled/package/src/contentElements/vrImage/VrImage.js b/entry_types/scrolled/package/src/contentElements/vrImage/VrImage.js index e900db6896..a76bc93dbb 100644 --- a/entry_types/scrolled/package/src/contentElements/vrImage/VrImage.js +++ b/entry_types/scrolled/package/src/contentElements/vrImage/VrImage.js @@ -32,11 +32,15 @@ export function VrImage({configuration, contentElementWidth}) { {renderLazyPanorama(configuration, imageFile, shouldLoad)} - + - + ); diff --git a/entry_types/scrolled/package/src/contentElements/vrImage/editor.js b/entry_types/scrolled/package/src/contentElements/vrImage/editor.js index c56b79f79f..f3b0378d09 100644 --- a/entry_types/scrolled/package/src/contentElements/vrImage/editor.js +++ b/entry_types/scrolled/package/src/contentElements/vrImage/editor.js @@ -31,6 +31,7 @@ editor.contentElementTypes.register('vrImage', { this.group('ContentElementPosition'); this.view(SeparatorView); this.group('ContentElementCaption', {entry}); + this.group('ContentElementInlineFileRightsSettings'); }); } }); diff --git a/entry_types/scrolled/package/src/editor/config.js b/entry_types/scrolled/package/src/editor/config.js index e17dd144e7..45acf398b0 100644 --- a/entry_types/scrolled/package/src/editor/config.js +++ b/entry_types/scrolled/package/src/editor/config.js @@ -75,3 +75,19 @@ editor.widgetTypes.register('defaultNavigation', { } }) }); + +editor.widgetTypes.register('textInlineFileRights', { + configurationEditorTabViewGroups: { + ContentElementInlineFileRightsSettings: function({disableWhenNoFileRights = true}) { + this.input('showTextInlineFileRightsBackdrop', CheckBoxInputView, { + disabledBindingModel: this.model.parent.transientState, + disabledBinding: 'hasFileRights', + disabled: hasFileRights => disableWhenNoFileRights && !hasFileRights, + displayUncheckedIfDisabled: true, + attributeTranslationKeyPrefixes: [ + 'pageflow_scrolled.editor.content_element_text_inline_file_rights_attributes' + ], + }); + } + } +}); diff --git a/entry_types/scrolled/package/src/frontend/InlineFileRights.js b/entry_types/scrolled/package/src/frontend/InlineFileRights.js index fa50bf5301..33f26aa367 100644 --- a/entry_types/scrolled/package/src/frontend/InlineFileRights.js +++ b/entry_types/scrolled/package/src/frontend/InlineFileRights.js @@ -9,7 +9,8 @@ import styles from './InlineFileRights.module.css'; export function InlineFileRights({items = [], context = 'standAlone', playerControlsFadedOut, - playerControlsStandAlone}) { + playerControlsStandAlone, + configuration = {}}) { const {t} = useI18n(); const filteredItems = items.filter(item => item.file && item.file.inlineRights && !isBlank(item.file.rights) @@ -21,7 +22,7 @@ export function InlineFileRights({items = [], return ( + props={{context, playerControlsFadedOut, playerControlsStandAlone, configuration}}>
    {filteredItems.map(({label, file}) =>
  • diff --git a/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.js b/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.js index 6809bc45ba..138c29aa06 100644 --- a/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.js +++ b/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.js @@ -1,19 +1,35 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import classNames from 'classnames'; -import {useDarkBackground} from 'pageflow-scrolled/frontend'; +import {useDarkBackground, useContentElementEditorState} from 'pageflow-scrolled/frontend'; import styles from './TextInlineFileRights.module.css'; -export function TextInlineFileRights({context, children}) { +export function TextInlineFileRights({configuration, context, children}) { const darkBackground = useDarkBackground(); + const {setTransientState} = useContentElementEditorState(); + const supported = context !== 'insideElement' && context !== 'playerControls'; - if (context === 'insideElement' || context === 'playerControls') { + useEffect(() => { + if (supported) { + setTransientState({hasFileRights: true}); + } + + return () => { + if (supported) { + setTransientState({hasFileRights: false}); + } + }; + }, [setTransientState, supported]); + + if (!supported) { return null; } return (
    {children} diff --git a/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.module.css b/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.module.css index 180203b34e..029d54132e 100644 --- a/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.module.css +++ b/entry_types/scrolled/package/src/widgets/textInlineFileRights/TextInlineFileRights.module.css @@ -25,26 +25,30 @@ display: none; } -.forSection { - position: absolute; - bottom: 0; - right: 0; -} - -.forSection li { +.withBackdrop li { padding: 0.1em 0.3em; border-radius: 0.25rem; - margin: 0 0.2em 0.1em auto; + margin: 0.2em 0 0 auto; background-color: color-mix(in srgb, lightContentSurfaceColor, transparent); color: darkContentTextColor; width: fit-content; } -.forSection.darkBackground li { +.withBackdrop.darkBackground li { background-color: color-mix(in srgb, darkContentSurfaceColor, transparent); color: lightContentTextColor; } +.forSection { + position: absolute; + bottom: 0; + right: 0; +} + +.forSection li { + margin: 0 0.2em 0.1em auto; +} + .text a { color: inherit; text-decoration-color: color-mix(in srgb, currentColor, transparent); From 149decfbe0905781f3f5432384a88f06a2261ce7 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 24 Oct 2024 12:45:22 +0200 Subject: [PATCH 5/5] Pin rackup to work around puma incompatibility issue with rackup 1.0.1 --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index a60622184a..29a2a595aa 100644 --- a/Gemfile +++ b/Gemfile @@ -34,3 +34,7 @@ gem 'capybara-chromedriver-logger', git: 'https://github.com/codevise/capybara-c # See https://github.com/charkost/prosopite/pull/79 gem 'prosopite', git: 'https://github.com/tf/prosopite', branch: 'location-backtrace-cleaner' + +# See https://github.com/rack/rackup/issues/22 +# Remove once https://github.com/puma/puma/pull/3532 is merged +gem 'rackup', '1.0.0', require: false