Add Tags into motion details
Adds tags to motion repository, view-motion and selectable for the motion detail view
This commit is contained in:
parent
f3c3d8ab8c
commit
f992b77d99
@ -31,6 +31,7 @@ import { ViewUnifiedChange } from '../../../site/motions/models/view-unified-cha
|
|||||||
import { ViewStatuteParagraph } from '../../../site/motions/models/view-statute-paragraph';
|
import { ViewStatuteParagraph } from '../../../site/motions/models/view-statute-paragraph';
|
||||||
import { Workflow } from '../../../shared/models/motions/workflow';
|
import { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
||||||
|
import { Tag } from 'app/shared/models/core/tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository Services for motions (and potentially categories)
|
* Repository Services for motions (and potentially categories)
|
||||||
@ -71,7 +72,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
private personalNoteService: PersonalNoteService,
|
private personalNoteService: PersonalNoteService,
|
||||||
private translate: TranslateService
|
private translate: TranslateService
|
||||||
) {
|
) {
|
||||||
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile]);
|
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile, Tag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,11 +91,23 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
const item = this.DS.get(Item, motion.agenda_item_id);
|
const item = this.DS.get(Item, motion.agenda_item_id);
|
||||||
const block = this.DS.get(MotionBlock, motion.motion_block_id);
|
const block = this.DS.get(MotionBlock, motion.motion_block_id);
|
||||||
const attachments = this.DS.getMany(Mediafile, motion.attachments_id);
|
const attachments = this.DS.getMany(Mediafile, motion.attachments_id);
|
||||||
|
const tags = this.DS.getMany(Tag, motion.tags_id);
|
||||||
let state: WorkflowState = null;
|
let state: WorkflowState = null;
|
||||||
if (workflow) {
|
if (workflow) {
|
||||||
state = workflow.getStateById(motion.state_id);
|
state = workflow.getStateById(motion.state_id);
|
||||||
}
|
}
|
||||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item, block, attachments);
|
return new ViewMotion(
|
||||||
|
motion,
|
||||||
|
category,
|
||||||
|
submitters,
|
||||||
|
supporters,
|
||||||
|
workflow,
|
||||||
|
state,
|
||||||
|
item,
|
||||||
|
block,
|
||||||
|
attachments,
|
||||||
|
tags
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,6 +232,26 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
await this.update(motion, viewMotion);
|
await this.update(motion, viewMotion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds new or removes existing tags from motions
|
||||||
|
*
|
||||||
|
* @param viewMotion the motion to tag
|
||||||
|
* @param tagId the tags id to add or remove
|
||||||
|
*/
|
||||||
|
public async setTag(viewMotion: ViewMotion, tagId: number): Promise<void> {
|
||||||
|
const motion = viewMotion.motion;
|
||||||
|
const tagIndex = motion.tags_id.findIndex(tag => tag === tagId);
|
||||||
|
|
||||||
|
if (tagIndex === -1) {
|
||||||
|
// add tag to motion
|
||||||
|
motion.tags_id.push(tagId);
|
||||||
|
} else {
|
||||||
|
// remove tag from motion
|
||||||
|
motion.tags_id.splice(tagIndex, 1);
|
||||||
|
}
|
||||||
|
await this.update(motion, viewMotion);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the submitters by sending a request to the server,
|
* Sets the submitters by sending a request to the server,
|
||||||
*
|
*
|
||||||
|
@ -303,6 +303,47 @@
|
|||||||
</mat-basic-chip>
|
</mat-basic-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tags -->
|
||||||
|
<!-- Disabled during "new motion" since changing has no effect -->
|
||||||
|
<div *ngIf="!editMotion && tagObserver.value.length > 0">
|
||||||
|
<h4 *ngIf="perms.isAllowed('change_metadata', motion) || motion.hasTags()">Tags</h4>
|
||||||
|
|
||||||
|
<!-- For privileged users -->
|
||||||
|
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||||
|
<!-- Selection menu -->
|
||||||
|
<mat-menu #tagMenu="matMenu">
|
||||||
|
<button mat-menu-item *ngFor="let tag of tagObserver.value" (click)="setTag($event, tag.id)">
|
||||||
|
<mat-icon *ngIf="motion.tags.includes(tag)">check</mat-icon>
|
||||||
|
{{ tag }}
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Make the whole container a trigger to prevent unexpected menu behavior -->
|
||||||
|
<div [matMenuTriggerFor]="tagMenu">
|
||||||
|
<!-- No selected tags -->
|
||||||
|
<mat-basic-chip *ngIf="!motion.hasTags()" class="grey" disabled>
|
||||||
|
{{ '–' }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
|
||||||
|
<!-- Display a chip list of tags -->
|
||||||
|
<mat-chip-list class="mat-chip-list-stacked">
|
||||||
|
<mat-basic-chip *ngFor="let tag of motion.tags" class="grey" disabled>
|
||||||
|
{{ tag }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- For non privileged users -->
|
||||||
|
<div *ngIf="!perms.isAllowed('change_metadata', motion)">
|
||||||
|
<mat-chip-list class="mat-chip-list-stacked">
|
||||||
|
<mat-basic-chip *ngFor="let tag of motion.tags" class="grey">
|
||||||
|
{{ tag }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Block -->
|
<!-- Block -->
|
||||||
<div *ngIf="!editMotion && blockObserver.value.length > 0">
|
<div *ngIf="!editMotion && blockObserver.value.length > 0">
|
||||||
<h4 translate>Motion block</h4>
|
<h4 translate>Motion block</h4>
|
||||||
@ -494,8 +535,13 @@
|
|||||||
|
|
||||||
<!-- The HTML Editor -->
|
<!-- The HTML Editor -->
|
||||||
<editor formControlName="text" [init]="tinyMceSettings" *ngIf="motion && editMotion" required></editor>
|
<editor formControlName="text" [init]="tinyMceSettings" *ngIf="motion && editMotion" required></editor>
|
||||||
<div *ngIf="contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)"
|
<div
|
||||||
class="red-warning-text" translate>
|
*ngIf="
|
||||||
|
contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)
|
||||||
|
"
|
||||||
|
class="red-warning-text"
|
||||||
|
translate
|
||||||
|
>
|
||||||
This field is required.
|
This field is required.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -506,15 +552,30 @@
|
|||||||
|
|
||||||
<!-- Reason -->
|
<!-- Reason -->
|
||||||
<div *ngIf="motion.reason || editMotion">
|
<div *ngIf="motion.reason || editMotion">
|
||||||
<h3 [ngClass]="(reasonRequired && contentForm.get('reason').invalid && (contentForm.get('reason').dirty || contentForm.get('reason').touched)) ? 'red-warning-text' : ''">
|
<h3
|
||||||
|
[ngClass]="
|
||||||
|
reasonRequired &&
|
||||||
|
contentForm.get('reason').invalid &&
|
||||||
|
(contentForm.get('reason').dirty || contentForm.get('reason').touched)
|
||||||
|
? 'red-warning-text'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
<span translate>Reason</span> <span *ngIf="reasonRequired && editMotion">*</span>
|
<span translate>Reason</span> <span *ngIf="reasonRequired && editMotion">*</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="motion-text" *ngIf="!editMotion"><div [innerHtml]="motion.reason"></div></div>
|
<div class="motion-text" *ngIf="!editMotion"><div [innerHtml]="motion.reason"></div></div>
|
||||||
|
|
||||||
<!-- The HTML Editor -->
|
<!-- The HTML Editor -->
|
||||||
<editor formControlName="reason" [init]="tinyMceSettings" *ngIf="editMotion" required></editor>
|
<editor formControlName="reason" [init]="tinyMceSettings" *ngIf="editMotion" required></editor>
|
||||||
<div *ngIf="reasonRequired && contentForm.get('reason').invalid && (contentForm.get('reason').dirty || contentForm.get('reason').touched)"
|
<div
|
||||||
class="red-warning-text" translate>
|
*ngIf="
|
||||||
|
reasonRequired &&
|
||||||
|
contentForm.get('reason').invalid &&
|
||||||
|
(contentForm.get('reason').dirty || contentForm.get('reason').touched)
|
||||||
|
"
|
||||||
|
class="red-warning-text"
|
||||||
|
translate
|
||||||
|
>
|
||||||
This field is required.
|
This field is required.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -275,3 +275,9 @@ span {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-chip-list-stacked {
|
||||||
|
.mat-chip {
|
||||||
|
margin: 4px 4px 4px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,6 +40,7 @@ import { ViewUnifiedChange } from '../../models/view-unified-change';
|
|||||||
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
|
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
|
||||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||||
|
import { Tag } from 'app/shared/models/core/tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the motion detail view
|
* Component for the motion detail view
|
||||||
@ -221,6 +222,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public agendaItemObserver: BehaviorSubject<Item[]>;
|
public agendaItemObserver: BehaviorSubject<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subject for tags
|
||||||
|
*/
|
||||||
|
public tagObserver: BehaviorSubject<Tag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the name of supporters are visible
|
* Determine if the name of supporters are visible
|
||||||
*/
|
*/
|
||||||
@ -356,6 +362,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
this.blockObserver = new BehaviorSubject(DS.getAll(MotionBlock));
|
this.blockObserver = new BehaviorSubject(DS.getAll(MotionBlock));
|
||||||
this.mediafilesObserver = new BehaviorSubject(DS.getAll(Mediafile));
|
this.mediafilesObserver = new BehaviorSubject(DS.getAll(Mediafile));
|
||||||
this.agendaItemObserver = new BehaviorSubject(DS.getAll(Item));
|
this.agendaItemObserver = new BehaviorSubject(DS.getAll(Item));
|
||||||
|
this.tagObserver = new BehaviorSubject(DS.getAll(Tag));
|
||||||
|
|
||||||
// Make sure the subjects are updated, when a new Model for the type arrives
|
// Make sure the subjects are updated, when a new Model for the type arrives
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
this.DS.changeObservable.subscribe(newModel => {
|
||||||
@ -372,6 +379,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
this.mediafilesObserver.next(DS.getAll(Mediafile));
|
this.mediafilesObserver.next(DS.getAll(Mediafile));
|
||||||
} else if (newModel instanceof Item) {
|
} else if (newModel instanceof Item) {
|
||||||
this.agendaItemObserver.next(DS.getAll(Item));
|
this.agendaItemObserver.next(DS.getAll(Item));
|
||||||
|
} else if (newModel instanceof Tag) {
|
||||||
|
this.tagObserver.next(DS.getAll(Tag));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1013,6 +1022,17 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or removes a tag to the current motion
|
||||||
|
*
|
||||||
|
* @param id Motion tag id
|
||||||
|
*/
|
||||||
|
public setTag(event: MouseEvent, id: number): void {
|
||||||
|
console.log('event: ', event);
|
||||||
|
event.stopPropagation();
|
||||||
|
this.repo.setTag(this.motion, id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the current motion to a motion block
|
* Add the current motion to a motion block
|
||||||
*
|
*
|
||||||
|
@ -12,6 +12,7 @@ import { ViewMotionCommentSection } from './view-motion-comment-section';
|
|||||||
import { Workflow } from '../../../shared/models/motions/workflow';
|
import { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
||||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
|
import { Tag } from 'app/shared/models/core/tag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The line numbering mode for the motion detail view.
|
* The line numbering mode for the motion detail view.
|
||||||
@ -51,6 +52,7 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
protected _item: Item;
|
protected _item: Item;
|
||||||
protected _block: MotionBlock;
|
protected _block: MotionBlock;
|
||||||
protected _attachments: Mediafile[];
|
protected _attachments: Mediafile[];
|
||||||
|
protected _tags: Tag[];
|
||||||
public personalNote: PersonalNoteContent;
|
public personalNote: PersonalNoteContent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,6 +234,10 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
return this._attachments ? this._attachments : null;
|
return this._attachments ? this._attachments : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get tags(): Tag[] {
|
||||||
|
return this._tags ? this._tags : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the creation date as Date object
|
* @returns the creation date as Date object
|
||||||
*/
|
*/
|
||||||
@ -313,7 +319,8 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
state?: WorkflowState,
|
state?: WorkflowState,
|
||||||
item?: Item,
|
item?: Item,
|
||||||
block?: MotionBlock,
|
block?: MotionBlock,
|
||||||
attachments?: Mediafile[]
|
attachments?: Mediafile[],
|
||||||
|
tags?: Tag[]
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._motion = motion;
|
this._motion = motion;
|
||||||
@ -325,6 +332,7 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
this._item = item;
|
this._item = item;
|
||||||
this._block = block;
|
this._block = block;
|
||||||
this._attachments = attachments;
|
this._attachments = attachments;
|
||||||
|
this._tags = tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
@ -364,6 +372,8 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
this.updateUser(update as User);
|
this.updateUser(update as User);
|
||||||
} else if (update instanceof Mediafile) {
|
} else if (update instanceof Mediafile) {
|
||||||
this.updateAttachments(update as Mediafile);
|
this.updateAttachments(update as Mediafile);
|
||||||
|
} else if (update instanceof Tag) {
|
||||||
|
this.updateTags(update as Tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,7 +391,7 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
/**
|
/**
|
||||||
* Update routine for the workflow
|
* Update routine for the workflow
|
||||||
*
|
*
|
||||||
* @param workflow potentially the changed workflow (state). Needs manual verification
|
* @param workflow potentially the (changed workflow (state). Needs manual verification
|
||||||
*/
|
*/
|
||||||
public updateWorkflow(workflow: Workflow): void {
|
public updateWorkflow(workflow: Workflow): void {
|
||||||
if (this.motion && workflow.id === this.motion.workflow_id) {
|
if (this.motion && workflow.id === this.motion.workflow_id) {
|
||||||
@ -443,6 +453,15 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateTags(update: Tag): void {
|
||||||
|
if (this.motion) {
|
||||||
|
if (this.tags_id && this.tags_id.includes(update.id)) {
|
||||||
|
const tagIndex = this.tags.findIndex(tag => tag.id === update.id);
|
||||||
|
this.tags[tagIndex] = update as Tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public hasSupporters(): boolean {
|
public hasSupporters(): boolean {
|
||||||
return !!(this.supporters && this.supporters.length > 0);
|
return !!(this.supporters && this.supporters.length > 0);
|
||||||
}
|
}
|
||||||
@ -451,6 +470,10 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
return !!(this.attachments && this.attachments.length > 0);
|
return !!(this.attachments && this.attachments.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasTags(): boolean {
|
||||||
|
return !!(this.tags && this.tags.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
public isStatuteAmendment(): boolean {
|
public isStatuteAmendment(): boolean {
|
||||||
return !!this.statute_paragraph_id;
|
return !!this.statute_paragraph_id;
|
||||||
}
|
}
|
||||||
@ -508,7 +531,8 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
this._state,
|
this._state,
|
||||||
this._item,
|
this._item,
|
||||||
this._block,
|
this._block,
|
||||||
this._attachments
|
this._attachments,
|
||||||
|
this._tags
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user