diff --git a/src/onegov/org/assets/js/many.jsx b/src/onegov/org/assets/js/many.jsx
index 36b0469388..f682cc8cae 100644
--- a/src/onegov/org/assets/js/many.jsx
+++ b/src/onegov/org/assets/js/many.jsx
@@ -453,11 +453,17 @@ function extractType(target) {
}
jQuery.fn.many = function() {
+ // Don't forget to change this file in town6 as well.
return this.each(function() {
var target = $(this);
var type = extractType(target);
- var data = JSON.parse(target.val());
+ try {
+ var data = JSON.parse(target.val());
+ } catch (e) {
+ console.log('Failed to parse JSON:', e);
+ return; // Skip this iteration if JSON is invalid
+ }
var label = target.closest('label');
var errors = label.siblings('.error');
@@ -470,7 +476,9 @@ jQuery.fn.many = function() {
label.hide();
errors.hide();
- var el = $('
');
+ // Create a unique wrapper for this instance
+ let wrapperId = 'many-wrapper-' + Math.random().toString(36).substr(2, 9);
+ let el = $('');
el.appendTo(label.parent());
// transfer javascript dependencies to the wrapper
diff --git a/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po
index a45c9c5d86..c57ba3fef8 100644
--- a/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po
+++ b/src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2025-01-30 15:51+0100\n"
+"POT-Creation-Date: 2025-01-28 17:16+0100\n"
"PO-Revision-Date: 2022-03-15 10:21+0100\n"
"Last-Translator: Marc Sommerhalder \n"
"Language-Team: German\n"
@@ -2808,6 +2808,24 @@ msgstr ""
msgid "Sidebar links"
msgstr "Links in der Sidebar"
+msgid "Contact link"
+msgstr "Kontaktlink"
+
+msgid "Sidebar contact"
+msgstr "Kontakt in der Sidebar"
+
+msgid "Please provide a URL if contact link text is set"
+msgstr "Bitte geben Sie eine URL an, wenn der Kontaktlink-Text gesetzt ist"
+
+msgid "Please provide link text if contact URL is set"
+msgstr "Bitte geben Sie einen Link-Text an, wenn die Kontakt-URL gesetzt ist"
+
+msgid "Contact Text"
+msgstr "Kontakttext"
+
+msgid "Contact URL"
+msgstr "Kontakt-URL"
+
msgid "Delete content when expired"
msgstr "Inhalt löschen nachdem abgelaufen"
@@ -6296,25 +6314,3 @@ msgstr "Ein Konto wurde für Sie erstellt"
msgid "The user was created successfully"
msgstr "Der Benutzer wurde erfolgreich erstellt"
-
-#~ msgid "Photo album. Will be shown at the end of content."
-#~ msgstr "Fotoalbum. Wird am Ende des Inhalts angezeigt."
-
-#~ msgid "Describes in detail how this form is to be used"
-#~ msgstr "Beschreibt detailliert wie dieses Formular benutzt werden soll"
-
-#~ msgid "Include logo in newsletter"
-#~ msgstr "Logo im Newsletter anzeigen"
-
-#~ msgid "Newsletter Subscription"
-#~ msgstr "Newsletter-Abonnement"
-
-#~ msgid ""
-#~ "Thank you for filling out this survey. If there's anything you'd like to "
-#~ "change, click on \"Edit\" below."
-#~ msgstr ""
-#~ "Vielen Dank für Ihre Teilnahme an dieser Umfrage. Falls Sie noch etwas "
-#~ "ändern möchten, klicken Sie auf den \"Bearbeiten\"-Button unten."
-
-#~ msgid "Rule updated"
-#~ msgstr "Regel aktualisiert"
diff --git a/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po
index feaac66372..012380548c 100644
--- a/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po
+++ b/src/onegov/org/locale/fr_CH/LC_MESSAGES/onegov.org.po
@@ -2808,6 +2808,24 @@ msgstr ""
msgid "Sidebar links"
msgstr "Liens dans la barre latérale"
+msgid "Contact link"
+msgstr "Lien de contact"
+
+msgid "Sidebar contact"
+msgstr "Contact dans la barre latérale"
+
+msgid "Please provide a URL if contact link text is set"
+msgstr "Veuillez fournir une URL si le texte du lien de contact est défini"
+
+msgid "Please provide link text if contact URL is set"
+msgstr "Veuillez indiquer le texte du lien si l'URL de contact est définie"
+
+msgid "Contact Text"
+msgstr "Lien de contact"
+
+msgid "Contact URL"
+msgstr "URL de contact"
+
msgid "Delete content when expired"
msgstr "Supprimer le contenu lorsqu'il est expiré"
@@ -6312,6 +6330,3 @@ msgstr "Un compte a été créé pour vous"
msgid "The user was created successfully"
msgstr "L'utilisateur a bien été créé"
-
-#~ msgid "Photo album. Will be shown at the end of content."
-#~ msgstr "Album photo. Sera affiché à la fin du contenu."
diff --git a/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po b/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po
index 33ecfaedee..ee27ac48a4 100644
--- a/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po
+++ b/src/onegov/org/locale/it_CH/LC_MESSAGES/onegov.org.po
@@ -2814,6 +2814,24 @@ msgstr ""
msgid "Sidebar links"
msgstr "Collegamenti della barra laterale"
+msgid "Contact link"
+msgstr "Link di contatto"
+
+msgid "Sidebar contact"
+msgstr "Contatto laterale"
+
+msgid "Please provide a URL if contact link text is set"
+msgstr "Si prega di fornire un URL se il testo del link di contatto è impostato"
+
+msgid "Please provide link text if contact URL is set"
+msgstr "Fornire il testo del link se l'URL di contatto è impostato"
+
+msgid "Contact Text"
+msgstr "Testo di contatto"
+
+msgid "Contact URL"
+msgstr "URL di contatto"
+
msgid "Delete content when expired"
msgstr "Eliminare il contenuto quando è scaduto"
@@ -6300,6 +6318,3 @@ msgstr "È stato creato un account per te"
msgid "The user was created successfully"
msgstr "Utente creato correttamente"
-
-#~ msgid "Photo album. Will be shown at the end of content."
-#~ msgstr "Album fotografico. Sarà mostrato alla fine del contenuto."
diff --git a/src/onegov/org/models/extensions.py b/src/onegov/org/models/extensions.py
index 0127273fd1..ae66bc988c 100644
--- a/src/onegov/org/models/extensions.py
+++ b/src/onegov/org/models/extensions.py
@@ -1087,6 +1087,115 @@ def links_to_json(
return SidebarLinksForm
+class SidebarContactLinkExtension(ContentExtension):
+ """ Like SidebarLinkExtension but the links are shown below the contact
+ field. We knowingly duplicate some code here .
+ """
+
+ sidepanel_contact = content_property()
+
+ def extend_form(
+ self,
+ form_class: type[FormT],
+ request: OrgRequest
+ ) -> type[FormT]:
+
+ class SidebarContactLinkForm(form_class): # type:ignore
+
+ sidepanel_contact = StringField(
+ label=_('Contact link'),
+ fieldset=_('Sidebar contact'),
+ render_kw={'class_': 'many many-links'}
+ )
+
+ if TYPE_CHECKING:
+ contact_errors: dict[int, str]
+ else:
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self.contact_errors = {}
+
+ def on_request(self) -> None:
+ if not self.sidepanel_contact.data:
+ self.sidepanel_contact.data = self.links_to_json(None)
+
+ def process_obj(self, obj: SidebarContactLinkExtension) -> None:
+ super().process_obj(obj)
+ if not obj.sidepanel_contact:
+ self.sidepanel_contact.data = self.links_to_json(None)
+ else:
+ self.sidepanel_contact.data = self.links_to_json(
+ obj.sidepanel_contact
+ )
+
+ def populate_obj(
+ self,
+ obj: SidebarContactLinkExtension,
+ *args: Any, **kwargs: Any
+ ) -> None:
+ super().populate_obj(obj, *args, **kwargs)
+ obj.sidepanel_contact = self.json_to_links(
+ self.sidepanel_contact.data) or None
+
+ def validate_sidepanel_contact(self, field: StringField) -> None:
+ for text, link in self.json_to_links(
+ self.sidepanel_contact.data
+ ):
+ if text and not link:
+ raise ValidationError(
+ _('Please provide a URL if contact link text is '
+ 'set'))
+ if link and not text:
+ raise ValidationError(
+ _('Please provide link text if contact URL is '
+ 'set'))
+ if link and not re.match(r'^(http://|https://|/)',
+ link):
+ raise ValidationError(
+ _('Your URLs must start with http://,'
+ ' https:// or /'
+ ' (for internal links)')
+ )
+
+ def json_to_links(
+ self,
+ text: str | None = None
+ ) -> list[tuple[str | None, str | None]]:
+
+ if not text:
+ return []
+
+ return [
+ (value['text'], link)
+ for value in json.loads(text).get('values', [])
+ if (link := value['link']) or value['text']
+ ]
+
+ def links_to_json(
+ self,
+ links: Sequence[tuple[str | None, str | None]] | None
+ ) -> str:
+ contact_links = links or []
+
+ return json.dumps({
+ 'labels': {
+ 'text': self.request.translate(_('Contact Text')),
+ 'link': self.request.translate(_('Contact URL')),
+ 'add': self.request.translate(_('Add')),
+ 'remove': self.request.translate(_('Remove')),
+ },
+ 'values': [
+ {
+ 'text': l[0],
+ 'link': l[1],
+ 'error': self.contact_errors.get(ix, '')
+ } for ix, l in enumerate(contact_links)
+ ]
+ })
+
+ return SidebarContactLinkForm
+
+
class DeletableContentExtension(ContentExtension):
""" Extends any class that has a meta dictionary field with the ability to
mark the content as deletable after reaching the end date. A cronjob will
diff --git a/src/onegov/org/models/page.py b/src/onegov/org/models/page.py
index adfa5f646b..4c04642f21 100644
--- a/src/onegov/org/models/page.py
+++ b/src/onegov/org/models/page.py
@@ -12,7 +12,7 @@
ContactExtension, ContactHiddenOnPageExtension,
PeopleShownOnMainPageExtension, ImageExtension,
NewsletterExtension, PublicationExtension, DeletableContentExtension,
- InlinePhotoAlbumExtension
+ InlinePhotoAlbumExtension, SidebarContactLinkExtension
)
from onegov.org.models.extensions import AccessExtension
from onegov.org.models.extensions import CoordinatesExtension
@@ -44,7 +44,7 @@ class Topic(Page, TraitInfo, SearchableContent, AccessExtension,
PeopleShownOnMainPageExtension, PersonLinkExtension,
CoordinatesExtension, ImageExtension,
GeneralFileLinkExtension, SidebarLinksExtension,
- InlinePhotoAlbumExtension):
+ SidebarContactLinkExtension, InlinePhotoAlbumExtension):
__mapper_args__ = {'polymorphic_identity': 'topic'}
diff --git a/src/onegov/org/theme/styles/org.scss b/src/onegov/org/theme/styles/org.scss
index 5c9aaeb2ce..dad027b0a0 100644
--- a/src/onegov/org/theme/styles/org.scss
+++ b/src/onegov/org/theme/styles/org.scss
@@ -6777,3 +6777,5 @@ a#results {
list-style: none;
}
}
+
+
diff --git a/src/onegov/town6/assets/js/many.jsx b/src/onegov/town6/assets/js/many.jsx
index 96b47644f2..a8fb49b78a 100644
--- a/src/onegov/town6/assets/js/many.jsx
+++ b/src/onegov/town6/assets/js/many.jsx
@@ -28,6 +28,7 @@ var ManyFields = React.createClass({
onChange={this.props.onChange}
/>
}
+
{
this.props.type === "opening-hours" &&
');
+ // Create a unique wrapper for this instance
+ let wrapperId = 'many-wrapper-' + Math.random().toString(36).substr(2, 9);
+ let el = $('');
el.appendTo(label.parent());
// transfer javascript dependencies to the wrapper
@@ -675,5 +685,6 @@ jQuery.fn.many = function() {
});
};
+
// since we intercept the dependency setup we need to run before document.ready
$('.many').many();
diff --git a/src/onegov/town6/templates/macros.pt b/src/onegov/town6/templates/macros.pt
index c4fc038477..09b3732186 100644
--- a/src/onegov/town6/templates/macros.pt
+++ b/src/onegov/town6/templates/macros.pt
@@ -711,13 +711,20 @@
-