diff --git a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.html b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.html index 1845e51093..ec27ae946f 100644 --- a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.html +++ b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.html @@ -1,35 +1,146 @@ -

Insert/edit link

-
-
- - URL - - +
+
+

Insert/edit external link

+
- -
- @if (data.needsText) { +
+@if (toggleExternalReference || !canEmbed) { +
+
+ + URL + + +
+
+ @if (data.needsText) { + + Text to display + + + } +
+
- Text to display - + Open link in ... + - } +
- -
- - Open link in ... - - +} +@if (canEmbed) { +
+
+

+ Link a topic/motion/assignment +

+ +
-
-
- + @if (toggleInternalReference) { +
+
+ + + {{ this.translate.instant('Topics') }} + + + {{ this.translate.instant('Motions') }} + + + {{ this.translate.instant('Assignments') }} + + + + + {{ this.translate.instant(searchLists[selectedRepoValue]) }} + + @for (searchRepo of searchRepos; track $index) { + @if (selectedRepoValue === $index) { + + } + } + +
+
+ +
+
+ } +} + +
+ @if (isUpdate) { - + } - +
diff --git a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.scss b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.scss index bc76a27e25..e56c2ad105 100644 --- a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.scss +++ b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.scss @@ -1,3 +1,41 @@ mat-form-field { width: 100%; } + +.radio-group { + display: flex; + gap: 15px; + margin-bottom: 5px; +} + +.references { + display: flex; + + .arrows { + display: flex; + margin-top: 7px; + margin-left: auto; + transform: translateX(-20px); + } +} + +.title { + padding: 0; +} + +.title:focus { + padding: 0; + outline: none; + box-shadow: none; + & h1 { + text-decoration: underline 2px #ccc; + border-radius: 25px; + } +} + +.clear-button { + display: flex; + justify-content: flex-end; + margin-top: -15px; + margin-bottom: -20px; +} diff --git a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.ts b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.ts index dd1dcde6f6..cfcbdc78bb 100644 --- a/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.ts +++ b/client/src/app/ui/modules/editor/components/editor-link-dialog/editor-link-dialog.component.ts @@ -1,5 +1,16 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, inject, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, UntypedFormGroup } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { AssignmentRepositoryService } from 'src/app/gateways/repositories/assignments/assignment-repository.service'; +import { MotionRepositoryService } from 'src/app/gateways/repositories/motions'; +import { TopicRepositoryService } from 'src/app/gateways/repositories/topics/topic-repository.service'; +import { getAgendaListMinimalSubscriptionConfig } from 'src/app/site/pages/meetings/pages/agenda/agenda.subscription'; +import { getAssignmentListMinimalSubscriptionConfig } from 'src/app/site/pages/meetings/pages/assignments/assignments.subscription'; +import { getMotionListMinimalSubscriptionConfig } from 'src/app/site/pages/meetings/pages/motions/motions.subscription'; +import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; +import { SubscribeToConfig } from 'src/app/site/services/model-request.service'; interface EditorLinkDialogInput { link?: { href: string; target?: string }; @@ -18,22 +29,118 @@ export interface EditorLinkDialogOutput { styleUrls: [`editor-link-dialog.component.scss`], standalone: false }) -export class EditorLinkDialogComponent { +export class EditorLinkDialogComponent implements OnInit { public isUpdate: boolean; public link: { href: string; target?: string }; - public text = ``; + public internalLink: { href: string; target?: string }; + public internalText = ``; + + public canEmbed: boolean; + + public toggleInternalReference: boolean; + public toggleExternalReference: boolean; + + // Subscriptions config + public searchSubscriptionConfig; + private activeMeetingIdService = inject(ActiveMeetingIdService); + public subscriptionTopicConfig: SubscribeToConfig; + public subscriptionMotionConfig: SubscribeToConfig; + public subscriptionAssignmentConfig: SubscribeToConfig; + + /** + * Boolean to decide, whether to open the extension-input and search-list. + */ + public editMode = false; + + /** + * Init Repos + */ + public agendaItemRepo = inject(TopicRepositoryService); + public motionItemRepo = inject(MotionRepositoryService); + public assignmentItemRepo = inject(AssignmentRepositoryService); + + public searchLists = ['Topic', 'Motion', 'Assignment']; + public searchRepos; + + /** + * FormGroup for the search-list. + */ + public internalReferenceForm: UntypedFormGroup; + + /** + * The selected internal item + */ + public item; + + /** + * Values selected by radio buttons + */ + public internalRadioOptions: FormGroup; + public selectedRepoValue = 0; + + /** + * Values for external link + */ + public externalLink: FormGroup; + public externalUrl: string; + public externalText: string; + public constructor( @Inject(MAT_DIALOG_DATA) public data: EditorLinkDialogInput, - private dialogRef: MatDialogRef + private dialogRef: MatDialogRef, + private router: Router, + private fb: FormBuilder, + public translate: TranslateService ) { - this.link = data.link; + // External reference + this.link = { ...data.link }; this.isUpdate = !!data.link && !!data.link.href; if (!this.link.target) { this.link.target = `_self`; } + this.externalLink = this.fb.group({ + extUrl: new FormControl(), + extText: new FormControl(), + extDisplayMode: new FormControl() + }); + this.externalLink.valueChanges.subscribe(() => { + this.externalUrl = this.externalLink.get('extUrl').value; + this.externalText = this.externalLink.get('extText').value; + this.link.target = this.externalLink.get('extDisplayMode').value; + }); + + // Internal reference + this.internalLink = { ...data.link }; + if (!this.internalLink.target) { + this.internalLink.target = `_blank`; + } + this.internalRadioOptions = this.fb.group({ + options: [0] + }); + this.internalRadioOptions.valueChanges.subscribe(() => { + this.selectedRepoValue = this.internalRadioOptions.get('options').value; + }); + } + + public ngOnInit(): void { + if ((this.canEmbed = !this.router.url.includes('motions') ? true : false)) { + this.searchRepos = [this.agendaItemRepo, this.motionItemRepo, this.assignmentItemRepo]; + this.searchSubscriptionConfig = [ + (this.subscriptionTopicConfig = getAgendaListMinimalSubscriptionConfig( + this.activeMeetingIdService.meetingId + )), + (this.subscriptionMotionConfig = getMotionListMinimalSubscriptionConfig( + this.activeMeetingIdService.meetingId + )), + (this.subscriptionAssignmentConfig = getAssignmentListMinimalSubscriptionConfig( + this.activeMeetingIdService.meetingId + )) + ]; + this.initForm(); + } } public removeLink(): void { @@ -45,14 +152,84 @@ export class EditorLinkDialogComponent { } public save(): void { - if (this.link.href && !/^[a-zA-Z]+:\/\//.test(this.link.href)) { - this.link.href = `http://` + this.link.href; + if (this.externalUrl) { + this.link.href = !/^[a-zA-Z]+:\/\//.test(this.externalUrl) + ? `http://` + this.externalUrl + : this.externalUrl; + if (this.data.needsText) { + this.dialogRef.close({ + action: `set-link`, + link: this.link, + text: this.externalText || this.link.href + }); + } else { + this.dialogRef.close({ action: `set-link`, link: this.link }); + } + } else { + this.changeEditMode(true); + if (this.internalLink.href) { + this.dialogRef.close({ + action: `set-link`, + text: this.internalText, + link: this.internalLink + }); + } } + } + + public toggleArrow(prop: 'toggleInternalReference' | 'toggleExternalReference'): void { + this[prop] = !this[prop]; + } - if (this.data.needsText) { - this.dialogRef.close({ action: `set-link`, link: this.link, text: this.text || this.link }); + /** + * Function to switch to or from editing-mode. + * + * @param save Boolean, whether the changes should be saved or resetted. + */ + public changeEditMode(save = false): void { + if (save) { + this.addReference(); } else { - this.dialogRef.close({ action: `set-link`, link: this.link }); + this.initForm(); + this.internalText = ``; } + this.editMode = !this.editMode; + } + + /** + * Initializes the form. + */ + public initForm(): void { + this.internalReferenceForm = new FormGroup({ + TopicFormControl: new FormControl(this.agendaItemRepo), + MotionFormControl: new FormControl(this.motionItemRepo), + AssignmentFormControl: new FormControl(this.assignmentItemRepo) + }); + this.internalReferenceForm.valueChanges.subscribe(() => { + const controlName = `${this.searchLists[this.selectedRepoValue]}FormControl`; + const selectedId = this.internalReferenceForm.get(controlName)?.value; + const repo = this.searchRepos[this.selectedRepoValue]; + this.item = repo.getViewModel(selectedId); + const action = this.item ? 'disable' : 'enable'; + ['extUrl', 'extText', 'extDisplayMode'].forEach(name => this.externalLink.get(name)?.[action]()); + + this.addReference(); + }); + } + + /** + * Function to add the values. + */ + public addReference(): void { + this.internalText = this.item ? `${this.item.getTitle()}` : ''; + this.internalLink.href = this.item ? this.urlBuilder(this.item) : ''; + } + + public urlBuilder(item): string { + const setCollection: string = item.collection === 'topic' ? 'agenda/topic' : item.collection; + const setId: number = item.collection === 'assignment' ? item.id : item.sequential_number; + const builtUrl = `${this.activeMeetingIdService.meetingId}/${setCollection}s/${setId}`; + const url = this.router.url.replace(/^\/.*$/, `/${builtUrl}`); + return url; } } diff --git a/client/src/app/ui/modules/editor/components/editor/editor.component.ts b/client/src/app/ui/modules/editor/components/editor/editor.component.ts index c7a24320ec..f98058285b 100644 --- a/client/src/app/ui/modules/editor/components/editor/editor.component.ts +++ b/client/src/app/ui/modules/editor/components/editor/editor.component.ts @@ -379,6 +379,7 @@ export class EditorComponent extends BaseFormControlComponent implements } ] }) + .insertContent({ type: `text`, text: ` ` }) .run(); } else { chain.setLink(result.link).run(); diff --git a/client/src/app/ui/modules/editor/editor.module.ts b/client/src/app/ui/modules/editor/editor.module.ts index ee73d107ca..5dafd45ab8 100644 --- a/client/src/app/ui/modules/editor/editor.module.ts +++ b/client/src/app/ui/modules/editor/editor.module.ts @@ -8,10 +8,12 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; +import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; import { MatTooltipModule } from '@angular/material/tooltip'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; import { MotionEditorComponent } from 'src/app/site/pages/meetings/pages/motions/components/motion-editor/motion-editor.component'; +import { SearchSelectorModule } from '../search-selector'; import { EditorComponent } from './components/editor/editor.component'; import { EditorEmbedDialogComponent } from './components/editor-embed-dialog/editor-embed-dialog.component'; import { EditorHtmlDialogComponent } from './components/editor-html-dialog/editor-html-dialog.component'; @@ -43,7 +45,10 @@ const DECLARATIONS = [ MatTooltipModule, FormsModule, ArrowNavigationDirective, - OpenSlidesTranslationModule.forChild() + OpenSlidesTranslationModule.forChild(), + SearchSelectorModule, + MatRadioButton, + MatRadioGroup ], exports: DECLARATIONS })