diff --git a/client/package.json b/client/package.json
index 54cae7dc5..dbef1d20f 100644
--- a/client/package.json
+++ b/client/package.json
@@ -46,6 +46,7 @@
"ngx-file-drop": "^5.0.0",
"ngx-mat-select-search": "^1.4.2",
"ngx-papaparse": "^3.0.2",
+ "pdfmake": "^0.1.40",
"po2json": "^1.0.0-alpha",
"rxjs": "^6.3.3",
"tinymce": "^4.9.0",
diff --git a/client/src/app/core/services/html-to-pdf.service.spec.ts b/client/src/app/core/services/html-to-pdf.service.spec.ts
new file mode 100644
index 000000000..72411a9fb
--- /dev/null
+++ b/client/src/app/core/services/html-to-pdf.service.spec.ts
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { HtmlToPdfService } from './html-to-pdf.service';
+
+describe('HtmlToPdfService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: HtmlToPdfService = TestBed.get(HtmlToPdfService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/client/src/app/core/services/html-to-pdf.service.ts b/client/src/app/core/services/html-to-pdf.service.ts
new file mode 100644
index 000000000..853952738
--- /dev/null
+++ b/client/src/app/core/services/html-to-pdf.service.ts
@@ -0,0 +1,452 @@
+import { Injectable } from '@angular/core';
+import { LineNumberingMode } from 'app/site/motions/models/view-motion';
+
+/**
+ * Converts HTML strings to pdfmake compatible document definition.
+ *
+ * TODO: Bring back upstream to pdfmake, so other projects may benefit from this converter and
+ * to exclude complex code from OpenSlides.
+ * Everything OpenSlides specific, such as line numbering and change recommendations,
+ * should be excluded from this and handled elsewhere.
+ *
+ * @example
+ * ```
+ * const dd = htmlToPdfService.convertHtml('
Hello World!
');
+ * ```
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class HtmlToPdfService {
+ /**
+ * holds the desired line number mode
+ */
+ private lineNumberingMode: LineNumberingMode;
+
+ /**
+ * Space between list elements
+ */
+ private LI_MARGIN_BOTTOM = 1.5;
+
+ /**
+ * Normal line height for paragraphs
+ */
+ private LINE_HEIGHT = 1.25;
+
+ /**
+ * space between paragraphs
+ */
+ private P_MARGIN_BOTTOM = 4.0;
+
+ /**
+ * Conversion of HTML tags into pdfmake directives
+ */
+ private elementStyles = {
+ // should be the same for most HTML code
+ b: ['font-weight:bold'],
+ strong: ['font-weight:bold'],
+ u: ['text-decoration:underline'],
+ em: ['font-style:italic'],
+ i: ['font-style:italic'],
+ h1: ['font-size:14', 'font-weight:bold'],
+ h2: ['font-size:12', 'font-weight:bold'],
+ h3: ['font-size:10', 'font-weight:bold'],
+ h4: ['font-size:10', 'font-style:italic'],
+ h5: ['font-size:10'],
+ h6: ['font-size:10'],
+ a: ['color:blue', 'text-decoration:underline'],
+ strike: ['text-decoration:line-through'],
+ // Pretty specific stuff that might be excluded for other projects than OpenSlides
+ del: ['color:red', 'text-decoration:line-through'],
+ ins: ['color:green', 'text-decoration:underline']
+ };
+
+ /**
+ * Treatment of required CSS-Classes
+ * Checking CSS is not possible
+ */
+ private classStyles = {
+ delete: ['color:red', 'text-decoration:line-through'],
+ insert: ['color:green', 'text-decoration:underline']
+ };
+
+ /**
+ * Constructor
+ */
+ public constructor() {}
+
+ /**
+ * Takes an HTML string, converts to HTML using a DOM parser and recursivly parses
+ * the content into pdfmake compatible doc definition
+ *
+ * @param htmlText the html text to translate as string
+ * @param lnMode determines the line numbering
+ * @returns pdfmake doc definition as object
+ */
+ public convertHtml(htmlText: string, lnMode?: LineNumberingMode): object {
+ const docDef = [];
+ this.lineNumberingMode = lnMode || LineNumberingMode.None;
+
+ // Cleanup of dirty html would happen here
+
+ // Create a HTML DOM tree out of html string
+ const parser = new DOMParser();
+ const parsedHtml = parser.parseFromString(htmlText, 'text/html');
+ // Since the spread operator did not work for HTMLCollection, use Array.from
+ const htmlArray = Array.from(parsedHtml.body.children);
+
+ // Parse the children of the current HTML element
+ for (const child of htmlArray) {
+ const parsedElement = this.parseElement(child);
+ docDef.push(parsedElement);
+ }
+
+ return docDef;
+ }
+
+ /**
+ * Converts a single HTML element to pdfmake, calls itself recursively for child html elements
+ *
+ * @param element can be an HTML element (
) or plain text ("Hello World")
+ * @param currentParagraph usually holds the parent element, to allow nested structures
+ * @param styles holds the style attributes of HTML elements (`
...`)
+ * @returns the doc def to the given element in consideration to the given paragraph and styles
+ */
+ public parseElement(element: any, styles?: string[]): any {
+ const nodeName = element.nodeName.toLowerCase();
+ let classes = [];
+ let newParagraph: any;
+
+ // extract explicit style information
+ styles = styles || [];
+
+ // to leave out plain text elements
+ if (element.getAttribute) {
+ const nodeStyle = element.getAttribute('style');
+ const nodeClass = element.getAttribute('class');
+
+ // add styles like `color:#ff00ff` content into styles array
+ if (nodeStyle) {
+ styles = nodeStyle
+ .split(';')
+ .map(style => style.replace(/\s/g, ''))
+ .concat(styles);
+ }
+
+ // Handle CSS classes
+ if (nodeClass) {
+ classes = nodeClass.toLowerCase().split(' ');
+
+ for (const cssClass of classes) {
+ if (this.classStyles[cssClass]) {
+ this.classStyles[cssClass].forEach(style => {
+ styles.push(style);
+ });
+ }
+ }
+ }
+ }
+
+ switch (nodeName) {
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6':
+ case 'p': {
+ const children = this.parseChildren(element, newParagraph);
+
+ if (this.lineNumberingMode === LineNumberingMode.Outside) {
+ newParagraph = this.create('stack');
+ newParagraph.stack = children;
+ } else {
+ newParagraph = this.create('text');
+ newParagraph.text = children;
+ }
+
+ newParagraph.margin = [0, this.P_MARGIN_BOTTOM];
+ newParagraph.lineHeight = this.LINE_HEIGHT;
+
+ styles = this.computeStyle(styles);
+ const implicitStyles = this.computeStyle(this.elementStyles[nodeName]);
+
+ newParagraph = {
+ ...newParagraph,
+ ...styles,
+ ...implicitStyles
+ };
+ break;
+ }
+ case 'a':
+ case 'b':
+ case 'strong':
+ case 'u':
+ case 'em':
+ case 'i':
+ case 'ins':
+ case 'del':
+ case 'strike': {
+ const children = this.parseChildren(element, styles.concat(this.elementStyles[nodeName]));
+ newParagraph = this.create('text');
+ newParagraph.text = children;
+ break;
+ }
+ case 'span': {
+ // Line numbering feature, will prevent compatibility to most other projects
+ if (element.getAttribute('data-line-number')) {
+ if (this.lineNumberingMode === LineNumberingMode.Inside) {
+ // TODO: algorithm for "inline" line numbers is not yet implemented
+ } else if (this.lineNumberingMode === LineNumberingMode.Outside) {
+ const currentLineNumber = element.getAttribute('data-line-number');
+ newParagraph = {
+ columns: [
+ // the line number column
+ {
+ width: 20,
+ text: currentLineNumber,
+ color: 'gray',
+ fontSize: 8,
+ margin: [0, 2, 0, 0],
+ lineHeight: this.LINE_HEIGHT
+ },
+ // target to push text into the line
+ {
+ text: []
+ }
+ ]
+ };
+ }
+ } else {
+ const children = this.parseChildren(element, styles);
+ newParagraph = {
+ ...this.create('text'),
+ ...this.computeStyle(styles)
+ };
+
+ newParagraph.text = children;
+ }
+ break;
+ }
+ case 'br': {
+ newParagraph = this.create('text');
+ // Add a dummy line, if the next tag is a BR tag again. The line could
+ // not be empty otherwise it will be removed and the empty line is not displayed
+ if (element.nextSibling && element.nextSibling.nodeName === 'BR') {
+ newParagraph.text.push(this.create('text', ' '));
+ }
+
+ newParagraph.lineHeight = this.LINE_HEIGHT;
+ break;
+ }
+ case 'li':
+ case 'div': {
+ newParagraph = this.create('text');
+ newParagraph.lineHeight = this.LI_MARGIN_BOTTOM;
+ newParagraph = {
+ ...newParagraph,
+ ...this.computeStyle(styles)
+ };
+
+ const children = this.parseChildren(element, newParagraph);
+ newParagraph = children;
+ break;
+ }
+ case 'ul':
+ case 'ol': {
+ newParagraph = this.create(nodeName);
+ const children = this.parseChildren(element, newParagraph);
+ newParagraph[nodeName] = children;
+ break;
+ }
+ default: {
+ newParagraph = {
+ ...this.create('text', element.textContent.replace(/\n/g, '')),
+ ...this.computeStyle(styles)
+ };
+ break;
+ }
+ }
+ return newParagraph;
+ }
+
+ /**
+ * Helper routine to parse an elements children and return the children as parsed pdfmake doc string
+ *
+ * @param element the parent element to parse
+ * @param currentParagraph the context of the element
+ * @param styles the styles array, usually just to parse back into the `parseElement` function
+ * @returns an array of parsed children
+ */
+ private parseChildren(element: any, styles?: Array): any {
+ const childNodes = element.childNodes;
+ const paragraph = [];
+ if (childNodes.length > 0) {
+ for (const child of childNodes) {
+ // skip empty child nodes
+ if (!(child.nodeName === '#text' && child.textContent.trim() === '')) {
+ const parsedElement = this.parseElement(child, styles);
+
+ if (
+ // add the line number column
+ this.lineNumberingMode === LineNumberingMode.Outside &&
+ child &&
+ child.classList &&
+ child.classList.contains('os-line-number')
+ ) {
+ paragraph.push(parsedElement);
+ } else if (
+ // if the first child of the parsed element is line number
+ this.lineNumberingMode === LineNumberingMode.Outside &&
+ element.firstChild &&
+ element.firstChild.classList &&
+ element.firstChild.classList.contains('os-line-number')
+ ) {
+ const currentLine = paragraph.pop();
+ // push the parsed element into the "text" array
+ currentLine.columns[1].text.push(parsedElement);
+ paragraph.push(currentLine);
+ } else {
+ paragraph.push(parsedElement);
+ }
+ }
+ }
+ }
+ return paragraph;
+ }
+
+ /**
+ * Extracts the style information from the given array
+ *
+ * @param styles an array of inline css styles (i.e. `style="margin: 10px"`)
+ * @returns an object with style pdfmake compatible style information
+ */
+ private computeStyle(styles: string[]): any {
+ const styleObject: any = {};
+ if (styles && styles.length > 0) {
+ for (const style of styles) {
+ const styleDefinition = style
+ .trim()
+ .toLowerCase()
+ .split(':');
+ const key = styleDefinition[0];
+ const value = styleDefinition[1];
+
+ if (styleDefinition.length === 2) {
+ switch (key) {
+ case 'padding-left': {
+ styleObject.margin = [+value, 0, 0, 0];
+ break;
+ }
+ case 'font-size': {
+ styleObject.fontSize = +value;
+ break;
+ }
+ case 'text-align': {
+ switch (value) {
+ case 'right':
+ case 'center':
+ case 'justify': {
+ styleObject.alignment = value;
+ break;
+ }
+ }
+ break;
+ }
+ case 'font-weight': {
+ switch (value) {
+ case 'bold': {
+ styleObject.bold = true;
+ break;
+ }
+ }
+ break;
+ }
+ case 'text-decoration': {
+ switch (value) {
+ case 'underline': {
+ styleObject.decoration = 'underline';
+ break;
+ }
+ case 'line-through': {
+ styleObject.decoration = 'lineThrough';
+ break;
+ }
+ }
+ break;
+ }
+ case 'font-style': {
+ switch (value) {
+ case 'italic': {
+ styleObject.italics = true;
+ break;
+ }
+ }
+ break;
+ }
+ case 'color': {
+ styleObject.color = this.parseColor(value);
+ break;
+ }
+ case 'background-color': {
+ styleObject.background = this.parseColor(value);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return styleObject;
+ }
+
+ /**
+ * Returns the color in a hex format (e.g. #12ff00).
+ * Also tries to convert RGB colors into hex values
+ *
+ * @param color color as string representation
+ * @returns color as hex values for pdfmake
+ */
+ private parseColor(color: string): string {
+ const haxRegex = new RegExp('^#([0-9a-f]{3}|[0-9a-f]{6})$');
+
+ // e.g. `#fff` or `#ff0048`
+ const rgbRegex = new RegExp('^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$');
+
+ // e.g. rgb(0,255,34) or rgb(22, 0, 0)
+ const nameRegex = new RegExp('^[a-z]+$');
+
+ if (haxRegex.test(color)) {
+ return color;
+ } else if (rgbRegex.test(color)) {
+ const decimalColors = rgbRegex.exec(color).slice(1);
+ for (let i = 0; i < 3; i++) {
+ let decimalValue = +decimalColors[i];
+ if (decimalValue > 255) {
+ decimalValue = 255;
+ }
+ let hexString = '0' + decimalValue.toString(16);
+ hexString = hexString.slice(-2);
+ decimalColors[i] = hexString;
+ }
+ return '#' + decimalColors.join('');
+ } else if (nameRegex.test(color)) {
+ return color;
+ } else {
+ console.error('Could not parse color "' + color + '"');
+ return color;
+ }
+ }
+
+ /**
+ * Helper function to create valid doc definitions container elements for pdfmake
+ *
+ * @param name should be a pdfMake container element, like 'text' or 'stack'
+ * @param content
+ */
+ private create(name: string, content?: any): any {
+ const container = {};
+ const docDef = content ? content : [];
+ container[name] = docDef;
+ return container;
+ }
+}
diff --git a/client/src/app/core/services/pdf-document.service.spec.ts b/client/src/app/core/services/pdf-document.service.spec.ts
new file mode 100644
index 000000000..f383a4995
--- /dev/null
+++ b/client/src/app/core/services/pdf-document.service.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+
+import { PdfDocumentService } from './pdf-document.service';
+import { E2EImportsModule } from 'e2e-imports.module';
+
+describe('PdfDocumentService', () => {
+ beforeEach(() =>
+ TestBed.configureTestingModule({
+ imports: [E2EImportsModule]
+ })
+ );
+
+ it('should be created', () => {
+ const service: PdfDocumentService = TestBed.get(PdfDocumentService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/client/src/app/core/services/pdf-document.service.ts b/client/src/app/core/services/pdf-document.service.ts
new file mode 100644
index 000000000..26b8a474e
--- /dev/null
+++ b/client/src/app/core/services/pdf-document.service.ts
@@ -0,0 +1,288 @@
+import { Injectable } from '@angular/core';
+import { saveAs } from 'file-saver';
+
+import * as pdfMake from 'pdfmake/build/pdfmake';
+import * as pdfFonts from 'pdfmake/build/vfs_fonts';
+import { TranslateService } from '@ngx-translate/core';
+
+import { ConfigService } from './config.service';
+
+/**
+ * TODO: Images and fonts
+ *
+ * Provides the general document structure for PDF documents, such as page margins, header, footer and styles.
+ * Also provides general purpose open and download functions.
+ *
+ * Use a local pdf service (i.e. MotionPdfService) to get the document definition for the content and use this service to
+ * open or download the pdf document
+ *
+ * @example
+ * ```ts
+ * const motionContent = this.motionPdfService.motionToDocDef(this.motion);
+ * this.this.pdfDocumentService.open(motionContent);
+ * ```
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class PdfDocumentService {
+ /**
+ * Constructor
+ *
+ * @param translate translations
+ * @param configService read config values
+ */
+ public constructor(private translate: TranslateService, private configService: ConfigService) {
+ // It should be possible to add own fonts here
+ pdfMake.vfs = pdfFonts.pdfMake.vfs;
+ }
+
+ /**
+ * Overall document definition and styles for the most PDF documents
+ *
+ * @param documentContent the content of the pdf as object
+ * @returns the pdf document definition ready to export
+ */
+ private getStandardPaper(documentContent: object, metadata?: object): object {
+ const standardFontSize = this.configService.instant('general_export_pdf_fontsize');
+
+ return {
+ pageSize: 'A4',
+ pageMargins: [75, 90, 75, 75],
+ defaultStyle: {
+ // TODO add fonts to vfs
+ // font: 'PdfFont',
+ fontSize: standardFontSize
+ },
+ header: this.getHeader(),
+ // TODO: option for no footer, wherever this can be defined
+ footer: (currentPage, pageCount) => {
+ return this.getFooter(currentPage, pageCount);
+ },
+ info: metadata,
+ content: documentContent,
+ styles: this.getStandardPaperStyles(),
+ images: this.getImageUrls()
+ };
+ }
+
+ /**
+ * Creates the header doc definition for normal PDF documents
+ *
+ * @returns an object that contains the necessary header definition
+ */
+ private getHeader(): object {
+ // check for the required logos
+ let logoHeaderLeftUrl = this.configService.instant('logo_pdf_header_L').path;
+ let logoHeaderRightUrl = this.configService.instant('logo_pdf_header_R').path;
+ let text;
+ const columns = [];
+
+ // add the left logo to the header column
+ if (logoHeaderLeftUrl) {
+ if (logoHeaderLeftUrl.indexOf('/') === 0) {
+ logoHeaderLeftUrl = logoHeaderLeftUrl.substr(1); // remove trailing /
+ }
+ columns.push({
+ image: logoHeaderLeftUrl,
+ fit: [180, 40],
+ width: '20%'
+ });
+ }
+
+ // add the header text if no logo on the right was specified
+ if (logoHeaderLeftUrl && logoHeaderRightUrl) {
+ text = '';
+ } else {
+ const line1 = [
+ this.translate.instant(this.configService.instant('general_event_name')),
+ this.translate.instant(this.configService.instant('general_event_description'))
+ ]
+ .filter(Boolean)
+ .join(' – ');
+ const line2 = [
+ this.configService.instant('general_event_location'),
+ this.configService.instant('general_event_date')
+ ]
+ .filter(Boolean)
+ .join(', ');
+ text = [line1, line2].join('\n');
+ }
+ columns.push({
+ text: text,
+ style: 'headerText',
+ alignment: logoHeaderRightUrl ? 'left' : 'right'
+ });
+
+ // add the logo to the right
+ if (logoHeaderRightUrl) {
+ if (logoHeaderRightUrl.indexOf('/') === 0) {
+ logoHeaderRightUrl = logoHeaderRightUrl.substr(1); // remove trailing /
+ }
+ columns.push({
+ image: logoHeaderRightUrl,
+ fit: [180, 40],
+ alignment: 'right',
+ width: '20%'
+ });
+ }
+
+ return {
+ color: '#555',
+ fontSize: 9,
+ margin: [75, 30, 75, 10], // [left, top, right, bottom]
+ columns: columns,
+ columnGap: 10
+ };
+ }
+
+ /**
+ * Creates the footer doc definition for normal PDF documents.
+ * Adds page numbers into the footer
+ *
+ * TODO: Add footer logos.
+ *
+ * @param currentPage holds the number of the current page
+ * @param pageCount holds the page count
+ * @returns the footer doc definition
+ */
+ private getFooter(currentPage: number, pageCount: number): object {
+ const columns = [];
+ let logoContainerWidth: string;
+ let pageNumberPosition: string;
+ let logoConteinerSize: Array;
+ let logoFooterLeftUrl = this.configService.instant('logo_pdf_footer_L').path;
+ let logoFooterRightUrl = this.configService.instant('logo_pdf_footer_R').path;
+
+ // if there is a single logo, give it a lot of space
+ if (logoFooterLeftUrl && logoFooterRightUrl) {
+ logoContainerWidth = '20%';
+ logoConteinerSize = [180, 40];
+ } else {
+ logoContainerWidth = '80%';
+ logoConteinerSize = [400, 50];
+ }
+
+ // the position of the page number depends on the logos
+ if (logoFooterLeftUrl && logoFooterRightUrl) {
+ pageNumberPosition = 'center';
+ } else if (logoFooterLeftUrl && !logoFooterRightUrl) {
+ pageNumberPosition = 'right';
+ } else if (logoFooterRightUrl && !logoFooterLeftUrl) {
+ pageNumberPosition = 'left';
+ } else {
+ pageNumberPosition = this.configService.instant('general_export_pdf_pagenumber_alignment');
+ }
+
+ // add the left footer logo, if any
+ if (logoFooterLeftUrl) {
+ if (logoFooterLeftUrl.indexOf('/') === 0) {
+ logoFooterLeftUrl = logoFooterLeftUrl.substr(1); // remove trailing /
+ }
+ columns.push({
+ image: logoFooterLeftUrl,
+ fit: logoConteinerSize,
+ width: logoContainerWidth,
+ alignment: 'left'
+ });
+ }
+
+ // add the page number
+ columns.push({
+ text: `${currentPage} / ${pageCount}`,
+ style: 'footerPageNumber',
+ alignment: pageNumberPosition
+ });
+
+ // add the right footer logo, if any
+ if (logoFooterRightUrl) {
+ if (logoFooterRightUrl.indexOf('/') === 0) {
+ logoFooterRightUrl = logoFooterRightUrl.substr(1); // remove trailing /
+ }
+ columns.push({
+ image: logoFooterRightUrl,
+ fit: logoConteinerSize,
+ width: logoContainerWidth,
+ alignment: 'right'
+ });
+ }
+
+ return {
+ margin: [75, 0, 75, 10],
+ columns: columns,
+ columnGap: 10
+ };
+ }
+
+ /**
+ * opens a pdf in a new tab
+ *
+ * @param docDefinition the structure of the PDF document
+ */
+ public open(docDefinition: object, metadata?: object): void {
+ pdfMake.createPdf(this.getStandardPaper(docDefinition, metadata)).open();
+ }
+
+ /**
+ * Downloads a pdf. Does not seem to work.
+ *
+ * @param docDefinition the structure of the PDF document
+ */
+ public download(docDefinition: object, filename: string, metadata?: object): void {
+ pdfMake
+ .createPdf(this.getStandardPaper(docDefinition, metadata))
+ .getBlob(blob => saveAs(blob, `${filename}.pdf`, { autoBOM: true }));
+ }
+
+ /**
+ * TODO
+ *
+ * Should create an images section in the document definition holding the base64 strings
+ * for the urls
+ *
+ * @returns an object containing the image names and the corresponding base64 strings
+ */
+ private getImageUrls(): object {
+ return {};
+ }
+
+ /**
+ * Definition of styles for standard papers
+ *
+ * @returns an object that contains all pdf styles
+ */
+ private getStandardPaperStyles(): object {
+ return {
+ title: {
+ fontSize: 18,
+ margin: [0, 0, 0, 20],
+ bold: true
+ },
+ subtitle: {
+ fontSize: 9,
+ margin: [0, -20, 0, 20],
+ color: 'grey'
+ },
+ headerText: {
+ fontSize: 10,
+ margin: [0, 10, 0, 0]
+ },
+ footerPageNumber: {
+ fontSize: 9,
+ margin: [0, 15, 0, 0],
+ color: '#555'
+ },
+ boldText: {
+ bold: true
+ },
+ smallText: {
+ fontSize: 8
+ },
+ heading3: {
+ fontSize: 12,
+ margin: [0, 10, 0, 0],
+ bold: true
+ }
+ };
+ }
+}
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 57b0b3da5..3c8c258d7 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
@@ -60,7 +60,8 @@