diff --git a/client/src/app/core/repositories/motions/motion-repository.service.ts b/client/src/app/core/repositories/motions/motion-repository.service.ts index eed5c50e1..0c76b9307 100644 --- a/client/src/app/core/repositories/motions/motion-repository.service.ts +++ b/client/src/app/core/repositories/motions/motion-repository.service.ts @@ -92,6 +92,22 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository | Partial) => { + if (motion.identifier) { + return motion.identifier + ': ' + motion.title; + } else { + return motion.title; + } + }; + + public getIdentifierOrTitle = (motion: Partial | Partial) => { + if (motion.identifier) { + return motion.identifier; + } else { + return motion.title; + } + }; + public getAgendaTitle = (motion: Partial | Partial) => { // if the identifier is set, the title will be 'Motion '. if (motion.identifier) { @@ -149,6 +165,8 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository this.getIdentifierOrTitle(viewMotion); + viewMotion.getTitle = () => this.getTitle(viewMotion); viewMotion.getVerboseName = this.getVerboseName; viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion); viewMotion.getProjectorTitle = viewMotion.getAgendaTitle; @@ -759,7 +777,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository -1 && (end > -1 && end > beg)) { - const id = Number(value.substring(beg + 8, end)); - const referedMotion = Number.isNaN(id) ? null : this.getViewModel(id); - const title = referedMotion ? referedMotion.identifier : '??'; - value = value.substring(0, beg) + title + value.substring(end + 1); - // recursively check for additional occurrences - return this.solveExtensionPlaceHolder(value); - } else { - return value; - } + public parseMotionPlaceholders(value: string): string { + return value.replace(/\[motion:(\d+)\]/g, (match, id) => { + const motion = this.getViewModel(id); + if (motion) { + return motion.getIdentifierOrTitle(); + } else { + return this.translate.instant(''); + } + }); } } diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html index 7867ce01c..d6b887cbd 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html @@ -278,11 +278,11 @@ *ngIf="motion.recommendation && motion.recommendation.show_recommendation_extension_field" class="spacer-top-10" > -
+ @@ -291,8 +291,8 @@ { + this.recommendationExtensionForm.get('motion_id').valueChanges.subscribe(change => { this.addMotionExtension(change); }); } @@ -1167,8 +1167,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, * @param id the ID of a selected motion returned by a search value selector */ public addMotionExtension(id: number): void { - const recoExtensionValue = this.searchMotionForm.get('recoExtension').value; - this.searchMotionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`); + const recoExtensionValue = this.recommendationExtensionForm.get('recoExtension').value || ''; + this.recommendationExtensionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`); } /** @@ -1185,7 +1185,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, * in {@link newRecommendationExtension} */ public setRecommendationExtension(): void { - this.repo.setRecommendationExtension(this.motion, this.searchMotionForm.get('recoExtension').value); + this.repo.setRecommendationExtension(this.motion, this.recommendationExtensionForm.get('recoExtension').value); } /** diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index 70deba75d..4a3292ae2 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -323,17 +323,27 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable { /** * This is set by the repository */ - public getAgendaTitle; + public getTitle: () => string; /** * This is set by the repository */ - public getAgendaTitleWithType; + public getIdentifierOrTitle: () => string; /** * This is set by the repository */ - public getVerboseName; + public getAgendaTitle: () => string; + + /** + * This is set by the repository + */ + public getAgendaTitleWithType: () => string; + + /** + * This is set by the repository + */ + public getVerboseName: () => string; public constructor( motion: Motion, @@ -362,14 +372,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable { this._parent = parent; } - public getTitle = () => { - if (this.identifier) { - return this.identifier + ': ' + this.title; - } else { - return this.title; - } - }; - public getAgendaItem(): ViewItem { return this.item; } diff --git a/client/src/app/slides/motions/motion-block/motion-block-slide-data.ts b/client/src/app/slides/motions/motion-block/motion-block-slide-data.ts index e27d0a6b7..4e91fd0da 100644 --- a/client/src/app/slides/motions/motion-block/motion-block-slide-data.ts +++ b/client/src/app/slides/motions/motion-block/motion-block-slide-data.ts @@ -1,6 +1,9 @@ -export interface MotionBlockSlideMotionRepresentation { +export interface MotionTitleInformation { title: string; identifier?: string; +} + +export interface MotionBlockSlideMotionRepresentation extends MotionTitleInformation { recommendation?: { name: string; css_class: string; @@ -11,4 +14,5 @@ export interface MotionBlockSlideMotionRepresentation { export interface MotionBlockSlideData { title: string; motions: MotionBlockSlideMotionRepresentation[]; + referenced_motions: { [id: number]: MotionTitleInformation }; } diff --git a/client/src/app/slides/motions/motion-block/motion-block-slide.component.html b/client/src/app/slides/motions/motion-block/motion-block-slide.component.html index 9389b057a..1e1f98ed2 100644 --- a/client/src/app/slides/motions/motion-block/motion-block-slide.component.html +++ b/client/src/app/slides/motions/motion-block/motion-block-slide.component.html @@ -6,7 +6,7 @@
- {{ motion.identifier }}: {{ motion.title }} + {{ getMotionTitle(motion) }}
diff --git a/client/src/app/slides/motions/motion-block/motion-block-slide.component.ts b/client/src/app/slides/motions/motion-block/motion-block-slide.component.ts index 037d458a8..0109cb7bf 100644 --- a/client/src/app/slides/motions/motion-block/motion-block-slide.component.ts +++ b/client/src/app/slides/motions/motion-block/motion-block-slide.component.ts @@ -1,8 +1,11 @@ import { Component } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; -import { MotionBlockSlideData, MotionBlockSlideMotionRepresentation } from './motion-block-slide-data'; -import { Motion } from 'app/shared/models/motions/motion'; +import { + MotionBlockSlideData, + MotionBlockSlideMotionRepresentation, + MotionTitleInformation +} from './motion-block-slide-data'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { StateCssClassMapping } from 'app/site/motions/models/view-workflow'; import { TranslateService } from '@ngx-translate/core'; @@ -17,8 +20,8 @@ export class MotionBlockSlideComponent extends BaseSlideComponent): string { - return this.motionRepo.getAgendaTitle(motion); + public getMotionTitle(motion: MotionTitleInformation): string { + return this.motionRepo.getTitle(motion); } public getStateCssColor(motion: MotionBlockSlideMotionRepresentation): string { @@ -28,7 +31,16 @@ export class MotionBlockSlideComponent extends BaseSlideComponent { + const titleInformation = this.data.data.referenced_motions[id]; + if (titleInformation) { + return this.motionRepo.getIdentifierOrTitle(titleInformation); + } else { + return this.translate.instant(''); + } + }); + + recommendation += ' ' + extension; } return recommendation; } diff --git a/openslides/motions/projector.py b/openslides/motions/projector.py index da00c3a32..e0d156652 100644 --- a/openslides/motions/projector.py +++ b/openslides/motions/projector.py @@ -1,3 +1,4 @@ +import re from typing import Any, Dict from ..users.projector import get_user_name @@ -224,7 +225,17 @@ def motion_block_slide( f"motion block with id {motion_block_id} does not exist" ) + # All motions in this motion block motions = [] + + # All motions meantioned in recommendations of the above motions. + # Only title_informations will be collected. With this, the client can + # replace the placeholders in the recommendation correctly + # This maps the motion id to the title information + referenced_motions: Dict[int, Dict[str, str]] = {} + motion_placeholder_regex = re.compile(r"\[motion:(\d+)\]") + + # Search motions. for motion in all_data["motions/motion"].values(): if motion["motion_block_id"] == motion_block_id: motion_object = { @@ -242,13 +253,35 @@ def motion_block_slide( "css_class": recommendation["css_class"], } if recommendation["show_recommendation_extension_field"]: - motion_object["recommendation_extension"] = motion[ - "recommendation_extension" + recommendation_extension = motion["recommendation_extension"] + + motion_object["recommendation_extension"] = recommendation_extension + # Collect all meantioned motions via [motion:] + referenced_ids = [ + int(id) + for id in motion_placeholder_regex.findall( + recommendation_extension + ) ] + for id in referenced_ids: + if ( + id not in referenced_motions + and id in all_data["motions/motion"] + ): + referenced_motions[id] = { + "title": all_data["motions/motion"][id]["title"], + "identifier": all_data["motions/motion"][id][ + "identifier" + ], + } motions.append(motion_object) - return {"title": motion_block["title"], "motions": motions} + return { + "title": motion_block["title"], + "motions": motions, + "referenced_motions": referenced_motions, + } def register_projector_slides() -> None: