From 6e4665041ebf68d9864ed4a9e10144213a8da414 Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Thu, 21 Jun 2018 15:46:59 +0200 Subject: [PATCH] Added page numbers and categories in TOC of motion PDF. --- CHANGELOG.rst | 16 +- bower.json | 2 +- openslides/core/static/js/core/pdf.js | 18 +- openslides/motions/static/js/motions/pdf.js | 183 ++++++++++++------- openslides/motions/static/js/motions/site.js | 2 +- 5 files changed, 144 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e8eb93fa6..1b8fe7ca3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ Motions: and list views for amendments) [#3637]. - New feature to customize workflows and states [#3772]. - New config options to show logos on the right side in PDF [#3768]. + - New table of contents with page numbers and categories in PDF [#3766]. + - Updated pdfMake to 0.1.37 [#3766]. Version 2.2 (2018-06-06) @@ -173,7 +175,7 @@ Agenda: as a slide or an overlay. - Manage speakers on the current list of speakers view. - List of speakers for hidden items is always visible. - + Core: - Added support for multiple projectors. - Added control for the resolution of the projectors. @@ -222,7 +224,7 @@ Core: - Moved full-text search to client-side (removed the server-side search engine Whoosh). - Made a lot of code clean up, improvements and bug fixes in client and backend. - + Motions: - Added adjustable line numbering mode (outside, inside, none) for each motion text. @@ -247,7 +249,7 @@ Motions: - Add new personal settings to remove all whitespaces from motion identifier. - Add new personal settings to allow amendments of amendments. - Added inline editing for comments. - + Elections: - Added options to calculate percentages on different bases. - Added calculation for required majority. @@ -255,7 +257,7 @@ Elections: - Removed unused assignment config to publish winner election results only. - Number of ballots printed can now be set in config. - Added inline edit field for a specific hint on ballot papers. - + Users: - Added new matrix-interface for managing groups and their permissions. - Added autoupdate on permission change (permission added). @@ -267,13 +269,13 @@ Users: - Allowed to import/export initial user password. - Added more multiselect actions. - Added QR code in users access pdf. - + Mediafiles: - Allowed to project uploaded images (png, jpg, gif) and video files (e. g. mp4, wmv, flv, quicktime, ogg). - Allowed to hide uploaded files in overview list for non authorized users. - Enabled removing of files from filesystem on model instance delete. - + Other: - Added Russian translation (Thanks to Andreas Engler). - Added command to create example data. @@ -563,7 +565,7 @@ Agenda: - New duration field for each item (with total time calculation for end time of event). - Better drag'n'drop sorting of agenda items (with nestedSortable jQuery plugin). Motions: - - Integrated CKEditor to use allowed HTML formatting in motion text/reason. + - Integrated CKEditor to use allowed HTML formatting in motion text/reason. With server-side whitelist filtering of HTML tags (with bleach) and HTML support for reportlab in motion pdf. - New motion API. diff --git a/bower.json b/bower.json index a6b407969..ccb38d1a6 100644 --- a/bower.json +++ b/bower.json @@ -37,7 +37,7 @@ "ngstorage": "~0.3.11", "ngBootbox": "~0.1.3", "papaparse": "~4.1.2", - "pdfmake": "0.1.33", + "pdfmake": "0.1.37", "roboto-fontface": "~0.6.0" }, "overrides": { diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js index d29d2bf18..1955ce284 100644 --- a/openslides/core/static/js/core/pdf.js +++ b/openslides/core/static/js/core/pdf.js @@ -360,9 +360,23 @@ angular.module('OpenSlidesApp.core.pdf', []) fontSize: 12, margin: [15,5] }, - tableofcontent: { + tocEntry: { fontSize: 12, - margin: [0,3] + margin: [0,0,0,0], + bold: false + }, + tocCategoryEntry: { + fontSize: 12, + margin: [10,0,0,0], + bold: false + }, + tocCategoryTitle: { + fontSize: 12, + margin: [0,0,0,0], + bold: true, + }, + tocCategorySection: { + margin: [0,0,0,10], }, listParent: { fontSize: 12, diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js index a06a03bef..ff574cdda 100644 --- a/openslides/motions/static/js/motions/pdf.js +++ b/openslides/motions/static/js/motions/pdf.js @@ -460,6 +460,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) return motion.identifier ? motion.identifier : ''; }; + var getId = function() { + return motion.id; + }; + var getCategory = function() { return motion.category; }; @@ -476,6 +480,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) getContent: getContent, getTitle: getTitle, getIdentifier: getIdentifier, + getId: getId, getCategory: getCategory, getImageMap: getImageMap, }); @@ -833,8 +838,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) * Constructor * @function * @param {object} allMotions - A sorted array of all motions to parse + * @param {string} sorting - The way the catalog has been sorted. Necessary for ToC */ - var createInstance = function(allMotions) { + var createInstance = function(allMotions, sorting) { var title = PDFLayout.createTitle( Config.translate(Config.get('motions_export_title').value) @@ -853,84 +859,130 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) }; var createTOContent = function() { - var heading = { - text: gettextCatalog.getString("Table of contents"), - style: "heading2" + var toc = []; + var exportCategory = (sorting === 'identifier' || sorting === 'category.prefix'); + var uniqueCategories = getUniqueCategories(); + var tocTitle = { + text: gettextCatalog.getString('Table of contents'), + style: 'heading2' }; - var toc = []; - angular.forEach(allMotions, function(motion) { - var identifier = motion.getIdentifier() ? motion.getIdentifier() : ''; - toc.push( - { - columns: [ - { - text: identifier, - style: 'tableofcontent', - width: 70 - }, - { - text: motion.getTitle(), - style: 'tableofcontent' - } - ] - } - ); + // all motions need a page ID. We use the motion identifier for that + _.forEach(allMotions, function (motion) { + motion.getContent()[0].id = ''+motion.getId(); }); + if (exportCategory && uniqueCategories) { + // own table per category + var catTocBody = []; + _.forEach(uniqueCategories, function (category) { + // push the name of the category + // make a table for correct alignment + catTocBody.push({ + table: { + body: [ + [ + { + text: category.prefix + ' - ' + category.name, + style: 'tocCategoryTitle' + } + ], + ] + }, + layout: 'noBorders', + }); + + var tocBody = []; + _.forEach(allMotions, function (motion) { + if (motion.getCategory() && category.name === motion.getCategory().name) { + tocBody.push(tocLine(motion, 'tocCategoryEntry')); + } + }); + catTocBody.push(tocTable(tocBody)); + }); + + //handle thouse without category + var uncatTocBody = []; + _.forEach(allMotions, function (motion) { + if (!motion.getCategory()) { + uncatTocBody.push(tocLine(motion, 'tocEntry')); + } + }); + + // only push this array if there is at least one entry + if (uncatTocBody.length > 0) { + catTocBody.push(tocTable(uncatTocBody)); + } + + toc.push(catTocBody); + } else { + // all categories in the same table + var tocBody = []; + _.forEach(allMotions, function (motion) { + tocBody.push(tocLine(motion, 'tocEntry')); + }); + toc.push(tocTable(tocBody)); + } + return [ - heading, + tocTitle, toc, PDFLayout.addPageBreak() ]; }; - // function to create the table of catergories (if any) - var createTOCategories = function() { + // creates a new table of contents table body + var tocTable = function (tocBody) { + return { + table: { + widths: ['auto', '*', 'auto'], + body: tocBody + }, + layout: 'noBorders', + style: 'tocCategorySection' + }; + }; + + // generates a line in the toc as list-object + var tocLine = function (motion, style) { + var firstColumn = ""; + if (motion.getIdentifier()) { + firstColumn = motion.getIdentifier(); + } + return [ + { + text: firstColumn, + style: style + }, + { + text: motion.getTitle(), + style: 'tocEntry' + }, + { + pageReference: ''+motion.getId(), + style: 'tocEntry', + alignment: 'right' + }, + ]; + }; + + // returns a list of unique category names + // necessary to create a ToC with categories + // if a motions without category is found, + // a corresponding entry should be added aswell + var getUniqueCategories = function() { var categories = []; _.forEach(allMotions, function (motion) { - var category = motion.getCategory(); - if (category) { - categories.push(category); - } - }); - var sortKey = Config.get('motions_export_category_sorting').value; - categories = _.orderBy(_.uniqBy(categories, 'id'), [sortKey]); - if (categories.length > 1) { - var heading = { - text: gettextCatalog.getString('Categories'), - style: 'heading2', - }; - - var toc = []; - angular.forEach(categories, function(cat) { - toc.push( + if (motion.getCategory()) { + categories.push( { - columns: [ - { - text: cat.prefix, - style: 'tableofcontent', - width: 50 - }, - { - text: cat.name, - style: 'tableofcontent' - } - ] + name: motion.getCategory().name, + prefix: motion.getCategory().prefix } ); - }); - - return [ - heading, - toc, - PDFLayout.addPageBreak() - ]; - } else { - // if there are no categories, return "empty string" - // pdfmake takes "null" literally and throws an error - return ""; - } + } + }); + return _.uniqBy(categories, 'name'); }; // returns the pure content of the motion, parseable by pdfmake @@ -948,7 +1000,6 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) content.push( title, createPreamble(), - createTOCategories(), createTOContent() ); } @@ -1253,7 +1304,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) if (singleMotion) { documentProviderPromise = PdfMakeDocumentProvider.createInstance(motionContentProviderArray[0]); } else { - var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray); + var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray, params.column); documentProviderPromise = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider); } documentProviderPromise.then(function (documentProvider) { diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 3a31f356b..ca1dab151 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -1454,7 +1454,7 @@ angular.module('OpenSlidesApp.motions.site', [ }; // Export dialog $scope.openExportDialog = function (motions) { - ngDialog.open(MotionExportForm.getDialog(motions)); + ngDialog.open(MotionExportForm.getDialog(motions, $scope.sort)); }; $scope.pdfExport = function (motions) { MotionPdfExport.export(motions);