diff --git a/core/docs/changelog/ZO-6370.change b/core/docs/changelog/ZO-6370.change new file mode 100644 index 0000000000..49f80d6ada --- /dev/null +++ b/core/docs/changelog/ZO-6370.change @@ -0,0 +1 @@ +ZO-6370: add image row module \ No newline at end of file diff --git a/core/src/zeit/content/article/edit/browser/configure.zcml b/core/src/zeit/content/article/edit/browser/configure.zcml index f3125cf720..6d6c051710 100644 --- a/core/src/zeit/content/article/edit/browser/configure.zcml +++ b/core/src/zeit/content/article/edit/browser/configure.zcml @@ -827,4 +827,46 @@ permission="zope.View" /> + + + + + + + + + + + + + + diff --git a/core/src/zeit/content/article/edit/browser/edit.py b/core/src/zeit/content/article/edit/browser/edit.py index c98746df03..ae1842fa2b 100644 --- a/core/src/zeit/content/article/edit/browser/edit.py +++ b/core/src/zeit/content/article/edit/browser/edit.py @@ -1,6 +1,7 @@ import json import logging +import grokcore.component as grok import zope.cachedescriptors.property import zope.component import zope.security @@ -575,3 +576,66 @@ class EditAnimation(zeit.cms.browser.manual.FormMixin, zeit.edit.browser.form.In @property def prefix(self): return 'animation.{0}'.format(self.context.__name__) + + +class IImageRowForm(zeit.content.article.edit.interfaces.IImageRow): + pass + + +@grok.implementer(IImageRowForm) +class ImageRowForm(grok.Adapter): + """ + We'd prefer Tuple(ReferenceField) over Tuple(Combination), but ReferenceSequenceWidget + nesting fails due to Mochikit-Sortable's limitations. + """ + + grok.context(zeit.content.article.edit.interfaces.IImageRow) + + @property + def images(self): + return [(x.target, x.caption, x.alt) for x in self.context.images] + + @images.setter + def images(self, value): + if not value: + self.context.images = () + return + result = [] + for img, caption, alt in value: + ref = self.context.images.create(img) + ref.caption = caption + ref.alt = alt + result.append(ref) + self.context.images = result + + +class EditImageRow(zeit.edit.browser.form.InlineForm): + legend = '' + form_fields = zope.formlib.form.FormFields(zeit.content.article.edit.interfaces.IImageRow).omit( + 'images', *list(zeit.edit.interfaces.IBlock) + ) + zope.formlib.form.FormFields(IImageRowForm).select('images') + + @property + def prefix(self): + return 'image_row.{0}'.format(self.context.__name__) + + def setUpWidgets(self, *args, **kw): + super().setUpWidgets(*args, **kw) + # s : float, m: column-width, l: large + max_images_dict = {'float': 1, 'column-width': 2, 'large': 3} + display_mode = self.context.display_mode + if display_mode is None: + return + max_images = max_images_dict.get(display_mode) + self.widgets['images'].context.max_length = max_images + + +class EditImageParallaxProperties(zeit.edit.browser.form.InlineForm): + legend = '' + form_fields = zope.formlib.form.FormFields( + zeit.content.article.edit.interfaces.IImageParallaxProperties + ).omit(*list(zeit.edit.interfaces.IBlock)) + + @property + def prefix(self): + return 'image_parallax_properties.{0}'.format(self.context.__name__) diff --git a/core/src/zeit/content/article/edit/browser/resources/editor.css b/core/src/zeit/content/article/edit/browser/resources/editor.css index 3da1ce0876..e1f7b3bc9b 100644 --- a/core/src/zeit/content/article/edit/browser/resources/editor.css +++ b/core/src/zeit/content/article/edit/browser/resources/editor.css @@ -2069,6 +2069,10 @@ height: 140px; } +.type-article .block.type-image_row .block-inner { + height: 800px; +} + /* .type-article .block.type-raw .block-inner is obsolete and can not be added anymore, see ZO-5487 */ diff --git a/core/src/zeit/content/article/edit/configure.zcml b/core/src/zeit/content/article/edit/configure.zcml index 53afc662a7..f9c24852e0 100644 --- a/core/src/zeit/content/article/edit/configure.zcml +++ b/core/src/zeit/content/article/edit/configure.zcml @@ -462,6 +462,28 @@ /> + + + + + + + + + + diff --git a/core/src/zeit/content/article/edit/image_row.py b/core/src/zeit/content/article/edit/image_row.py new file mode 100644 index 0000000000..4c482007d6 --- /dev/null +++ b/core/src/zeit/content/article/edit/image_row.py @@ -0,0 +1,41 @@ +import grokcore.component as grok + +from zeit.cms.content.property import ObjectPathAttributeProperty +from zeit.cms.i18n import MessageFactory as _ +import zeit.cms.content.reference +import zeit.content.article.edit.block +import zeit.content.article.edit.interfaces + + +@grok.implementer(zeit.content.article.edit.interfaces.IImageRow) +class ImageRow(zeit.content.article.edit.block.Block): + type = 'image_row' + display_mode = zeit.cms.content.property.ObjectPathAttributeProperty('.', 'display_mode') + variant_name = zeit.cms.content.property.ObjectPathAttributeProperty('.', 'variant_name') + images = zeit.cms.content.reference.ReferenceProperty('.image', 'image') + + +class RowFactory(zeit.content.article.edit.block.BlockFactory): + produces = ImageRow + title = _('Image Row') + + +@grok.implementer(zeit.content.article.edit.interfaces.IImageParallaxProperties) +class ImageParallaxProperties(zeit.content.article.edit.block.Block): + type = 'image_parallax_properties' + + show_caption = ObjectPathAttributeProperty( + '.', + 'show_caption', + zeit.content.article.edit.interfaces.IImageParallaxProperties['show_caption'], + ) + show_source = ObjectPathAttributeProperty( + '.', + 'show_source', + zeit.content.article.edit.interfaces.IImageParallaxProperties['show_source'], + ) + + +class PropertiesFactory(zeit.content.article.edit.block.BlockFactory): + produces = ImageParallaxProperties + title = _('Image Parallax Properties') diff --git a/core/src/zeit/content/article/edit/interfaces.py b/core/src/zeit/content/article/edit/interfaces.py index ee9672e368..5a50bc0696 100644 --- a/core/src/zeit/content/article/edit/interfaces.py +++ b/core/src/zeit/content/article/edit/interfaces.py @@ -658,3 +658,39 @@ class IIngredientDice(IBlock): """ pass + + +class IImageRow(IBlock): + images = zope.schema.Tuple( + title=_('Images'), + max_length=3, + default=(), + required=False, + value_type=zc.form.field.Combination( + fields=( + zope.schema.Choice( + title=_('Image'), source=zeit.content.image.interfaces.ImageGroupSource() + ), + zope.schema.TextLine(title=_('Image caption'), required=False), + zope.schema.TextLine(title=_('Alternative Text'), required=False), + ) + ), + ) + display_mode = zope.schema.Choice( + title=_('Display Mode'), + source=zeit.content.article.source.IMAGE_DISPLAY_MODE_SOURCE, + default='column-width', + required=False, + ) + # Currently need default for bw compat. + variant_name = zope.schema.Choice( + title=_('Variant Name'), + source=zeit.content.article.source.IMAGE_VARIANT_NAME_SOURCE, + default='wide', + required=False, + ) + + +class IImageParallaxProperties(IBlock): + show_caption = zope.schema.Bool(title=_('Show caption'), required=False, default=True) + show_source = zope.schema.Bool(title=_('Show source'), required=False, default=True) diff --git a/core/src/zeit/content/article/edit/tests/modules.xml b/core/src/zeit/content/article/edit/tests/modules.xml index de62ac7594..85dc913582 100644 --- a/core/src/zeit/content/article/edit/tests/modules.xml +++ b/core/src/zeit/content/article/edit/tests/modules.xml @@ -29,4 +29,6 @@ Video Ausgabe ARD Video + Bilderreihe + Bilder-Parallax-Modul diff --git a/core/src/zeit/content/article/edit/tests/test_image_row.py b/core/src/zeit/content/article/edit/tests/test_image_row.py new file mode 100644 index 0000000000..2ccfbf516e --- /dev/null +++ b/core/src/zeit/content/article/edit/tests/test_image_row.py @@ -0,0 +1,40 @@ +import lxml.builder + +from zeit.cms.content.reference import ReferenceProperty +from zeit.cms.interfaces import ICMSContent +from zeit.cms.testcontenttype.testcontenttype import ExampleContentType +from zeit.content.article.edit.image_row import ImageParallaxProperties, ImageRow +import zeit.content.article.testing +import zeit.edit.interfaces + + +class ImageRowTest(zeit.content.article.testing.FunctionalTestCase): + def get_image_row(self): + image_row = ImageRow(None, lxml.builder.E.image_row()) + return image_row + + def test_image_row_should_be_set(self): + ExampleContentType.images = ReferenceProperty('.body.image', 'image') + image_row = self.get_image_row() + image_row.display_mode = 'square' + image_row.variant_name = 'default' + image = ICMSContent('http://xml.zeit.de/2006/DSC00109_2.JPG') + content = self.repository['testcontent'] + ref = content.images.create(image) + content.images = (ref,) + ref.title = 'localtitle' + ref.caption = 'localcaption' + image_row.images = (ref,) + self.assertEqual('square', image_row.xml.xpath('.')[0].get('display_mode')) + self.assertEqual('default', image_row.xml.xpath('.')[0].get('variant_name')) + self.assertEqual( + 'http://xml.zeit.de/2006/DSC00109_2.JPG', image_row.xml.xpath('./image')[0].get('src') + ) + + def test_image_parallax_properties_should_be_set(self): + image_properties = ImageParallaxProperties(None, lxml.builder.E.image_parallax_properties()) + + image_properties.show_source = True + image_properties.show_caption = True + self.assertEqual('True', image_properties.xml.xpath('.')[0].get('show_source')) + self.assertEqual('True', image_properties.xml.xpath('.')[0].get('show_caption')) diff --git a/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.mo b/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.mo index 3b814f613d..1b4dab1fee 100644 Binary files a/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.mo and b/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.mo differ diff --git a/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.po b/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.po index d82044a124..76628dacf3 100644 --- a/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.po +++ b/core/src/zeit/locales/de/LC_MESSAGES/zeit.cms.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: Unknown\n" -"POT-Creation-Date: Tue Nov 19 14:24:41 2024\n" +"POT-Creation-Date: Tue Nov 26 17:02:26 2024\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -190,7 +190,7 @@ msgid "Edit" msgstr "Bearbeiten" #: zeit/cms/browser/form.py:293 zeit/cms/repository/browser/file.py:89 -#: zeit/content/article/edit/browser/edit.py:299 +#: zeit/content/article/edit/browser/edit.py:300 #: zeit/content/article/edit/browser/form.py:469 #: zeit/content/article/edit/browser/push.py:52 #: zeit/content/article/edit/browser/reference.py:54 @@ -1504,6 +1504,7 @@ msgstr "Farbe Button" #: zeit/content/advertisement/interfaces.py:33 #: zeit/content/article/edit/image.py:85 #: zeit/content/article/edit/interfaces.py:199 +#: zeit/content/article/edit/interfaces.py:672 #: zeit/content/article/interfaces.py:68 zeit/content/cp/interfaces.py:126 #: zeit/content/cp/interfaces.py:284 zeit/content/cp/interfaces.py:626 #: zeit/content/image/browser/configure.zcml:262 @@ -1560,7 +1561,7 @@ msgid "View metadata" msgstr "Metadaten anzeigen" #: zeit/content/animation/browser/form.py:21 -#: zeit/content/animation/interfaces.py:28 zeit/content/article/article.py:232 +#: zeit/content/animation/interfaces.py:28 zeit/content/article/article.py:217 #: zeit/content/article/browser/configure.zcml:74 #: zeit/content/article/edit/browser/form.py:58 msgid "Article" @@ -1607,6 +1608,7 @@ msgid "Video to use for animation" msgstr "Video" #: zeit/content/animation/interfaces.py:44 +#: zeit/content/article/edit/interfaces.py:665 #: zeit/content/gallery/browser/configure.zcml:79 #: zeit/content/image/browser/configure.zcml:167 #: zeit/content/image/browser/imagebrowser.py:6 @@ -1689,21 +1691,21 @@ msgstr "zuletzt veröffentlicht am ${date} um ${time} von ${by}" msgid "not published" msgstr "Nicht veröffentlicht" -#: zeit/content/article/edit/browser/edit.py:102 +#: zeit/content/article/edit/browser/edit.py:103 #: zeit/content/cp/browser/landing.py:53 msgid "The object \"${name}\" does not exist." msgstr "Das Objekt »${name}« konnte nicht gefunden werden." -#: zeit/content/article/edit/browser/edit.py:110 +#: zeit/content/article/edit/browser/edit.py:111 msgid "Could not create block for \"${name}\", because I don't know which one." msgstr "Kann für \"${name}\" keinen Block anlegen, weil ich nicht weiß welchen" -#: zeit/content/article/edit/browser/edit.py:303 +#: zeit/content/article/edit/browser/edit.py:304 msgid "generate-video-recommendation" msgstr "Videoempfehlung generieren" -#: zeit/content/article/edit/browser/edit.py:341 -#: zeit/content/article/edit/browser/edit.py:346 +#: zeit/content/article/edit/browser/edit.py:342 +#: zeit/content/article/edit/browser/edit.py:347 #: zeit/content/author/browser/form.py:170 #: zeit/content/author/browser/honorar.py:53 #: zeit/content/volume/browser/form.py:142 @@ -1924,6 +1926,14 @@ msgstr "Seitenumbruch" msgid "Embed block" msgstr "Easy-Embed" +#: zeit/content/article/edit/image_parallax_properties.py:29 +msgid "Image Parallax Properties" +msgstr "Bilder-Parallax-Eigenschaften" + +#: zeit/content/article/edit/image_row.py:56 +msgid "Image Row" +msgstr "Bilderreihe" + #: zeit/content/article/edit/ingredientdice.py:15 msgid "Ingredient dice block" msgstr "Zutatenwürfel bearbeiten" @@ -1986,10 +1996,12 @@ msgid "Edited" msgstr "redigiert" #: zeit/content/article/edit/interfaces.py:209 +#: zeit/content/article/edit/interfaces.py:680 msgid "Display Mode" msgstr "Darstellung" #: zeit/content/article/edit/interfaces.py:217 +#: zeit/content/article/edit/interfaces.py:687 #: zeit/content/article/interfaces.py:76 msgid "Variant Name" msgstr "Seitenverhältnis" @@ -2210,6 +2222,23 @@ msgstr "Ziehen Sie einen Artikel/Link hierher" msgid "Filter" msgstr "Filter" +#: zeit/content/article/edit/interfaces.py:674 +#: zeit/content/gallery/interfaces.py:102 +msgid "Image caption" +msgstr "Bildunterschrift" + +#: zeit/content/article/edit/interfaces.py:675 +msgid "Alternative Text" +msgstr "ALT-Text" + +#: zeit/content/article/edit/interfaces.py:695 +msgid "Show caption" +msgstr "Bildunterschrift anzeigen" + +#: zeit/content/article/edit/interfaces.py:696 +msgid "Show source" +msgstr "Bildquelle anzeigen" + #: zeit/content/article/edit/jobticker.py:18 msgid "Jobbox ticker block" msgstr "Jobticker Modul" @@ -2962,7 +2991,7 @@ msgid "Could not publish ${id} since it has validation errors." msgstr "" "${id} hat Validierungsfehler und kann daher nicht veröffentlicht werden." -#: zeit/content/cp/centerpage.py:177 +#: zeit/content/cp/centerpage.py:165 msgid "Centerpage 2009" msgstr "Center-Page" @@ -3381,10 +3410,6 @@ msgstr "" msgid "Is a cropped image of" msgstr "Ist ein Zuschnitt von" -#: zeit/content/gallery/interfaces.py:102 -msgid "Image caption" -msgstr "Bildunterschrift" - #: zeit/content/gallery/interfaces.py:103 msgid "gallery-image-caption-description" msgstr "" @@ -4814,6 +4839,12 @@ msgstr "Format" msgid "with info" msgstr "mit Info" +#~ msgid "Caption" +#~ msgstr "Bildunterschrift" + +#~ msgid "Alt-Text" +#~ msgstr "Alternativtext" + #~ msgid "Print Ressort" #~ msgstr "Print-Ressort" diff --git a/core/src/zeit/locales/zeit.cms.pot b/core/src/zeit/locales/zeit.cms.pot index a55e7e0d66..2caff6dfa2 100644 --- a/core/src/zeit/locales/zeit.cms.pot +++ b/core/src/zeit/locales/zeit.cms.pot @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: Unknown\n" -"POT-Creation-Date: Tue Nov 19 14:24:41 2024\n" +"POT-Creation-Date: Tue Nov 26 17:02:26 2024\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: Zope 3 Developers \n" @@ -209,7 +209,7 @@ msgstr "" #: zeit/cms/browser/form.py:293 #: zeit/cms/repository/browser/file.py:89 -#: zeit/content/article/edit/browser/edit.py:299 +#: zeit/content/article/edit/browser/edit.py:300 #: zeit/content/article/edit/browser/form.py:469 #: zeit/content/article/edit/browser/push.py:52 #: zeit/content/article/edit/browser/reference.py:54 @@ -1541,6 +1541,7 @@ msgstr "" #: zeit/content/advertisement/interfaces.py:33 #: zeit/content/article/edit/image.py:85 #: zeit/content/article/edit/interfaces.py:199 +#: zeit/content/article/edit/interfaces.py:672 #: zeit/content/article/interfaces.py:68 #: zeit/content/cp/interfaces.py:126 #: zeit/content/cp/interfaces.py:284 @@ -1603,7 +1604,7 @@ msgstr "" #: zeit/content/animation/browser/form.py:21 #: zeit/content/animation/interfaces.py:28 -#: zeit/content/article/article.py:232 +#: zeit/content/article/article.py:217 #: zeit/content/article/browser/configure.zcml:74 #: zeit/content/article/edit/browser/form.py:58 msgid "Article" @@ -1652,6 +1653,7 @@ msgid "Video to use for animation" msgstr "" #: zeit/content/animation/interfaces.py:44 +#: zeit/content/article/edit/interfaces.py:665 #: zeit/content/gallery/browser/configure.zcml:79 #: zeit/content/image/browser/configure.zcml:167 #: zeit/content/image/browser/imagebrowser.py:6 @@ -1736,21 +1738,21 @@ msgstr "" msgid "not published" msgstr "" -#: zeit/content/article/edit/browser/edit.py:102 +#: zeit/content/article/edit/browser/edit.py:103 #: zeit/content/cp/browser/landing.py:53 msgid "The object \"${name}\" does not exist." msgstr "" -#: zeit/content/article/edit/browser/edit.py:110 +#: zeit/content/article/edit/browser/edit.py:111 msgid "Could not create block for \"${name}\", because I don't know which one." msgstr "" -#: zeit/content/article/edit/browser/edit.py:303 +#: zeit/content/article/edit/browser/edit.py:304 msgid "generate-video-recommendation" msgstr "" -#: zeit/content/article/edit/browser/edit.py:341 -#: zeit/content/article/edit/browser/edit.py:346 +#: zeit/content/article/edit/browser/edit.py:342 +#: zeit/content/article/edit/browser/edit.py:347 #: zeit/content/author/browser/form.py:170 #: zeit/content/author/browser/honorar.py:53 #: zeit/content/volume/browser/form.py:142 @@ -1979,6 +1981,14 @@ msgstr "" msgid "Embed block" msgstr "" +#: zeit/content/article/edit/image_parallax_properties.py:29 +msgid "Image Parallax Properties" +msgstr "" + +#: zeit/content/article/edit/image_row.py:56 +msgid "Image Row" +msgstr "" + #: zeit/content/article/edit/ingredientdice.py:15 msgid "Ingredient dice block" msgstr "" @@ -2044,10 +2054,12 @@ msgid "Edited" msgstr "" #: zeit/content/article/edit/interfaces.py:209 +#: zeit/content/article/edit/interfaces.py:680 msgid "Display Mode" msgstr "" #: zeit/content/article/edit/interfaces.py:217 +#: zeit/content/article/edit/interfaces.py:687 #: zeit/content/article/interfaces.py:76 msgid "Variant Name" msgstr "" @@ -2274,6 +2286,23 @@ msgstr "" msgid "Filter" msgstr "" +#: zeit/content/article/edit/interfaces.py:674 +#: zeit/content/gallery/interfaces.py:102 +msgid "Image caption" +msgstr "" + +#: zeit/content/article/edit/interfaces.py:675 +msgid "Alternative Text" +msgstr "" + +#: zeit/content/article/edit/interfaces.py:695 +msgid "Show caption" +msgstr "" + +#: zeit/content/article/edit/interfaces.py:696 +msgid "Show source" +msgstr "" + #: zeit/content/article/edit/jobticker.py:18 msgid "Jobbox ticker block" msgstr "" @@ -3034,7 +3063,7 @@ msgstr "" msgid "Could not publish ${id} since it has validation errors." msgstr "" -#: zeit/content/cp/centerpage.py:177 +#: zeit/content/cp/centerpage.py:165 msgid "Centerpage 2009" msgstr "" @@ -3453,10 +3482,6 @@ msgstr "" msgid "Is a cropped image of" msgstr "" -#: zeit/content/gallery/interfaces.py:102 -msgid "Image caption" -msgstr "" - #: zeit/content/gallery/interfaces.py:103 msgid "gallery-image-caption-description" msgstr ""