From 26cc6556a03819417fa68757e8722be8a51bf9f9 Mon Sep 17 00:00:00 2001 From: GabrielMeyer Date: Tue, 4 Jun 2019 14:41:12 +0200 Subject: [PATCH] Implementation of exporting submitters and recommendation The user can choose the option to export the informtion about submitters and the recommendation including the extension of motions to the 'table of contents'. --- client/src/app/core/marked-translations.ts | 8 +- .../core/ui-services/pdf-document.service.ts | 69 ++++++++-- .../services/motion-pdf-catalog.service.ts | 121 ++++++++++++++---- openslides/motions/config_variables.py | 20 ++- 4 files changed, 174 insertions(+), 44 deletions(-) diff --git a/client/src/app/core/marked-translations.ts b/client/src/app/core/marked-translations.ts index 3c91bd041..c89164e72 100644 --- a/client/src/app/core/marked-translations.ts +++ b/client/src/app/core/marked-translations.ts @@ -151,9 +151,11 @@ _('Number of all delegates'); _('Number of all participants'); _('Use the following custom number'); _('Custom number of ballot papers'); -// subgroup export -_('Title for PDF and DOCX documents (all motions)'); -_('Preamble text for PDF and DOCX documents (all motions)'); +// subgroup PDF export +_('PDF export'); +_('Title for PDF documents of motions'); +_('Preamble text for PDF documents of motions'); +_('Show submitters and recommendation in table of contents'); _('Sort categories by'); _('Sort motions by'); _('Include the sequential number in PDF and DOCX'); diff --git a/client/src/app/core/ui-services/pdf-document.service.ts b/client/src/app/core/ui-services/pdf-document.service.ts index b19d89784..fceefb169 100644 --- a/client/src/app/core/ui-services/pdf-document.service.ts +++ b/client/src/app/core/ui-services/pdf-document.service.ts @@ -13,9 +13,20 @@ import { HttpService } from '../core-services/http.service'; */ export enum StyleType { DEFAULT = 'tocEntry', + SUBTITLE = 'subtitle', + SUB_ENTRY = 'tocSubEntry', CATEGORY_SECTION = 'tocCategorySection' } +/** + * Enumeration to describe the type of borders. + */ +export enum BorderType { + DEFAULT = 'noBorders', + LIGHT_HORIZONTAL_LINES = 'lightHorizontalLines', + HEADER_ONLY = 'headerLineOnly' +} + /** * Custom PDF error class to handle errors in a safer way */ @@ -445,9 +456,10 @@ export class PdfDocumentService { * @returns an object that contains all pdf styles */ private getStandardPaperStyles(): object { + const pageSize = this.configService.instant('general_export_pdf_pagesize'); return { title: { - fontSize: 18, + fontSize: pageSize === 'A5' ? 14 : 16, margin: [0, 0, 0, 20], bold: true }, @@ -475,12 +487,12 @@ export class PdfDocumentService { fontSize: 8 }, heading2: { - fontSize: 14, + fontSize: pageSize === 'A5' ? 12 : 14, margin: [0, 0, 0, 10], bold: true }, heading3: { - fontSize: 12, + fontSize: pageSize === 'A5' ? 10 : 12, margin: [0, 10, 0, 0], bold: true }, @@ -498,17 +510,25 @@ export class PdfDocumentService { margin: [15, 5] }, tocEntry: { - fontSize: 12, + fontSize: pageSize === 'A5' ? 10 : 11, margin: [0, 0, 0, 0], bold: false }, + tocHeaderRow: { + fontSize: 8, + italics: true + }, + tocSubEntry: { + fontSize: pageSize === 'A5' ? 9 : 10, + color: '#404040' + }, tocCategoryEntry: { - fontSize: 12, + fontSize: pageSize === 'A5' ? 10 : 11, margin: [10, 0, 0, 0], bold: false }, tocCategoryTitle: { - fontSize: 12, + fontSize: pageSize === 'A5' ? 10 : 11, margin: [0, 0, 0, 4], bold: true }, @@ -610,6 +630,7 @@ export class PdfDocumentService { */ public createTitle(configVariable: string): object { const titleText = this.translate.instant(this.configService.instant(configVariable)); + return { text: titleText, style: 'title' @@ -647,13 +668,21 @@ export class PdfDocumentService { * @param tocBody the body of the table * @returns The table of contents as doc definition */ - public createTocTableDef(tocBody: object, style: StyleType = StyleType.DEFAULT): object { + public createTocTableDef( + tocBody: object[], + style: StyleType = StyleType.DEFAULT, + borderStyle: BorderType = BorderType.DEFAULT, + ...header: object[] + ): object { return { table: { + headerRows: header[0] ? header.length : 0, + keepWithHeaderRows: header[0] ? header.length : 0, + dontBreakRows: true, widths: ['auto', '*', 'auto'], - body: tocBody + body: header[0] ? [...header, ...tocBody] : tocBody }, - layout: 'noBorders', + layout: borderStyle, style: style }; } @@ -672,15 +701,16 @@ export class PdfDocumentService { identifier: string, title: string, pageReference: string, - style: StyleType = StyleType.DEFAULT - ): Object { + style: StyleType = StyleType.DEFAULT, + ...subTitle: object[] + ): Array { return [ { text: identifier, style: style }, { - text: title, + text: [title, ...subTitle], style: 'tocEntry' }, { @@ -690,4 +720,19 @@ export class PdfDocumentService { } ]; } + + /** + * Function to create an inline line in the toc. + * + * @param text The text for the line. + * @param bold Optional boolean, if the text should be bold - defaults to `false`. + * + * @returns {Object} An object for `DocDefinition` for `pdf-make`. + */ + public createTocLineInline(text: string): Object { + return { + text: '\n' + text, + style: StyleType.SUB_ENTRY + }; + } } diff --git a/client/src/app/site/motions/services/motion-pdf-catalog.service.ts b/client/src/app/site/motions/services/motion-pdf-catalog.service.ts index d61c14d94..6e0b5fbf2 100644 --- a/client/src/app/site/motions/services/motion-pdf-catalog.service.ts +++ b/client/src/app/site/motions/services/motion-pdf-catalog.service.ts @@ -6,7 +6,8 @@ import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-mo import { MotionPdfService, InfoToExport } from './motion-pdf.service'; import { ConfigService } from 'app/core/ui-services/config.service'; import { ViewCategory } from '../models/view-category'; -import { PdfError, PdfDocumentService, StyleType } from 'app/core/ui-services/pdf-document.service'; +import { PdfError, PdfDocumentService, StyleType, BorderType } from 'app/core/ui-services/pdf-document.service'; +import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; /** * Service to export a list of motions. @@ -31,7 +32,8 @@ export class MotionPdfCatalogService { private translate: TranslateService, private configService: ConfigService, private motionPdfService: MotionPdfService, - private pdfService: PdfDocumentService + private pdfService: PdfDocumentService, + private motionRepo: MotionRepositoryService ) {} /** @@ -116,13 +118,15 @@ export class MotionPdfCatalogService { text: this.translate.instant('Table of contents'), style: 'heading2' }; + const exportSubmitterRecommendation = this.configService.instant( + 'motions_export_submitter_recommendation' + ); - if (!sorting) { - sorting = this.configService.instant('motions_export_category_sorting'); - } - const exportCategory = sorting === 'identifier' || sorting === 'prefix'; + // Initialize the header and the layout for border-style. + const header = exportSubmitterRecommendation ? this.getTocHeaderDefinition() : undefined; + const layout = exportSubmitterRecommendation ? BorderType.LIGHT_HORIZONTAL_LINES : BorderType.DEFAULT; - if (exportCategory && categories) { + if (categories && categories.length) { const catTocBody = []; for (const category of categories) { // push the name of the category @@ -138,27 +142,38 @@ export class MotionPdfCatalogService { ] ] }, - layout: 'noBorders' + layout: exportSubmitterRecommendation ? 'lightHorizontalLines' : 'noBorders' }); - const tocBody = motions - .filter(motion => category === motion.category) - .map(motion => - this.pdfService.createTocLine( - `${motion.identifier}`, - motion.title, - `${motion.id}`, - StyleType.CATEGORY_SECTION - ) - ); + const tocBody = []; + for (const motion of motions.filter(motionIn => category === motionIn.category)) { + if (exportSubmitterRecommendation) { + tocBody.push(this.appendSubmittersAndRecommendation(motion, StyleType.CATEGORY_SECTION)); + } else { + tocBody.push( + this.pdfService.createTocLine( + `${motion.identifier ? motion.identifier : ''}`, + motion.title, + `${motion.id}`, + StyleType.CATEGORY_SECTION + ) + ); + } + } - catTocBody.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION)); + catTocBody.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION, layout, header)); } // handle those without category const uncatTocBody = motions .filter(motion => !motion.category) - .map(motion => this.pdfService.createTocLine(`${motion.identifier}`, motion.title, `${motion.id}`)); + .map(motion => + this.pdfService.createTocLine( + `${motion.identifier ? motion.identifier : ''}`, + motion.title, + `${motion.id}` + ) + ); // only push this array if there is at least one entry if (uncatTocBody.length > 0) { @@ -168,15 +183,46 @@ export class MotionPdfCatalogService { toc.push(catTocBody); } else { // all motions in the same table - const tocBody = motions.map(motion => - this.pdfService.createTocLine(`${motion.identifier}`, motion.title, `${motion.id}`) - ); - toc.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION)); + const tocBody = []; + for (const motion of motions) { + if (exportSubmitterRecommendation) { + tocBody.push(this.appendSubmittersAndRecommendation(motion)); + } else { + tocBody.push( + this.pdfService.createTocLine( + `${motion.identifier ? motion.identifier : ''}`, + motion.title, + `${motion.id}` + ) + ); + } + } + toc.push(this.pdfService.createTocTableDef(tocBody, StyleType.CATEGORY_SECTION, layout, header)); } return [tocTitle, toc, this.pdfService.getPageBreak()]; } + /** + * Function to get the definition for the header + * for exporting motion-list as PDF. Needed, if submitters + * and recommendation should also be exported to the `Table of Contents`. + * + * @returns {object} The DocDefinition for `pdfmake.js`. + */ + private getTocHeaderDefinition(): object { + return [ + { text: this.translate.instant('Identifier'), style: 'tocHeaderRow' }, + { + text: `${this.translate.instant('Title')} · ${this.translate.instant( + 'Submitters' + )} · ${this.translate.instant('Recommendation')}`, + style: 'tocHeaderRow' + }, + { text: this.translate.instant('Page'), style: 'tocHeaderRow', alignment: 'right' } + ]; + } + /** * Extract the used categories from the given motion list. * @@ -198,4 +244,31 @@ export class MotionPdfCatalogService { return categories; } + + /** + * Creates lines for the `Table of contents` containing submitters and recommendation. + * + * @param motion The motion containing the information + * @param style Optional `StyleType`. Defaults to `tocEntry`. + * + * @returns {Array} An array containing the `DocDefinitions` for `pdf-make`. + */ + private appendSubmittersAndRecommendation(motion: ViewMotion, style: StyleType = StyleType.DEFAULT): Array { + const recommendation = this.motionRepo.getExtendedRecommendationLabel(motion); + let submitterList = ''; + for (let i = 0; i < motion.submitters.length; ++i) { + submitterList += + i !== motion.submitters.length - 1 + ? motion.submitters[i].getTitle() + ', ' + : motion.submitters[i].getTitle(); + } + return this.pdfService.createTocLine( + `${motion.identifier ? motion.identifier : ''}`, + motion.title, + `${motion.id}`, + style, + this.pdfService.createTocLineInline(submitterList), + this.pdfService.createTocLineInline(recommendation) + ); + } } diff --git a/openslides/motions/config_variables.py b/openslides/motions/config_variables.py index b3c0f73c0..8f58db0af 100644 --- a/openslides/motions/config_variables.py +++ b/openslides/motions/config_variables.py @@ -344,22 +344,32 @@ def get_config_variables(): validators=(MinValueValidator(1),), ) - # PDF and DOCX export + # PDF export yield ConfigVariable( name="motions_export_title", default_value="Motions", - label="Title for PDF and DOCX documents (all motions)", + label="Title for PDF document of motions", weight=370, group="Motions", - subgroup="Export", + subgroup="PDF export", ) yield ConfigVariable( name="motions_export_preamble", default_value="", - label="Preamble text for PDF and DOCX documents (all motions)", + label="Preamble text for PDF documents of motions", weight=375, group="Motions", - subgroup="Export", + subgroup="PDF export", + ) + + yield ConfigVariable( + name="motions_export_submitter_recommendation", + default_value=False, + label="Show submitters and recommendation in table of contents", + input_type="boolean", + weight=378, + group="Motions", + subgroup="PDF export", )