diff --git a/CHANGELOG b/CHANGELOG index 2836d3c1f..30caafee6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,6 +46,7 @@ Core: - Added watching permissions in client and change the view immediately on changes. - Validate HTML strings from CKEditor against XSS attacks. - Added success/error symbol to config to show if saving was successful. +- Added UTF-8 byte order mark for every CSV export. Motions: - Added adjustable line numbering mode (outside, inside, none) for each diff --git a/openslides/agenda/static/js/agenda/csv.js b/openslides/agenda/static/js/agenda/csv.js index e0347d4b4..513c5d26c 100644 --- a/openslides/agenda/static/js/agenda/csv.js +++ b/openslides/agenda/static/js/agenda/csv.js @@ -16,7 +16,7 @@ angular.module('OpenSlidesApp.agenda.csv', []) }); }; return { - export: function (element, agenda) { + export: function (agenda) { var csvRows = [ makeHeaderline() ]; @@ -32,7 +32,7 @@ angular.module('OpenSlidesApp.agenda.csv', []) row.push('"' + (item.is_hidden ? '1' : '') + '"'); csvRows.push(row); }); - CsvDownload(csvRows, element, 'agenda-export.csv'); + CsvDownload(csvRows, 'agenda-export.csv'); }, }; } diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index 24cd5135f..9d274f43b 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -270,8 +270,7 @@ angular.module('OpenSlidesApp.agenda.site', [ PdfCreate.download(documentProvider.getDocument(), filename); }; $scope.csvExport = function () { - var element = document.getElementById('downloadLinkCSV'); - AgendaCsvExport.export(element, $scope.itemsFiltered); + AgendaCsvExport.export($scope.itemsFiltered); }; /** select mode functions **/ diff --git a/openslides/core/config_variables.py b/openslides/core/config_variables.py index 7e09e2667..d6fc369d2 100644 --- a/openslides/core/config_variables.py +++ b/openslides/core/config_variables.py @@ -105,6 +105,16 @@ def get_config_variables(): group='General', subgroup='System') + # CSV + + yield ConfigVariable( + name='general_csv_separator', + default_value=',', + label='The separator used for the csv export and examples', + weight=144, + group='General', + subgroup='CSV') + # Projector yield ConfigVariable( diff --git a/openslides/core/static/js/core/csv.js b/openslides/core/static/js/core/csv.js index 0e9ef32ff..8361bbf7e 100644 --- a/openslides/core/static/js/core/csv.js +++ b/openslides/core/static/js/core/csv.js @@ -5,19 +5,17 @@ angular.module('OpenSlidesApp.core.csv', []) .factory('CsvDownload', [ - function () { - return function (contentRows, element, fileName) { - if (navigator.msSaveBlob && typeof navigator.msSaveBlob === 'function') { - // Bad browsers - var blob = new Blob([contentRows.join('\r\n')]); - navigator.msSaveBlob(blob, fileName); - } else { // Good browsers - // %0A is the url encoded linefeed character. Needed to be - // percentage encoded for the data url. - element.href = 'data:text/csv;charset=utf-8,' + contentRows.join('%0A'); - element.download = fileName; - element.target = '_blank'; - } + 'Config', + 'FileSaver', + function (Config, FileSaver) { + var utf8_BOM = decodeURIComponent('%EF%BB%BF'); + return function (contentRows, filename) { + var separator = Config.get('general_csv_separator').value; + var rows = _.map(contentRows, function (row) { + return row.join(separator); + }); + var blob = new Blob([utf8_BOM + rows.join('\n')]); + FileSaver.saveAs(blob, filename); }; } ]); diff --git a/openslides/motions/static/js/motions/csv.js b/openslides/motions/static/js/motions/csv.js index b722e999b..1fdba56e2 100644 --- a/openslides/motions/static/js/motions/csv.js +++ b/openslides/motions/static/js/motions/csv.js @@ -15,7 +15,7 @@ angular.module('OpenSlidesApp.motions.csv', []) }); }; return { - export: function (element, motions) { + export: function (motions) { var csvRows = [ makeHeaderline() ]; @@ -32,16 +32,16 @@ angular.module('OpenSlidesApp.motions.csv', []) row.push('"' + motion.origin + '"'); csvRows.push(row); }); - CsvDownload(csvRows, element, 'motions-export.csv'); + CsvDownload(csvRows, 'motions-export.csv'); }, - downloadExample: function (element) { + downloadExample: function () { var csvRows = [makeHeaderline(), // example entries ['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A'], - ['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', '' ], - ['' , 'Title 3', 'Text 3', '' , '' , '' , '' ], + ['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', ''], + ['' , 'Title 3', 'Text 3', '', '', '', ''], ]; - CsvDownload(csvRows, element, 'motions-example.csv'); + CsvDownload(csvRows, 'motions-example.csv'); }, }; } diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 1b1c18b5c..e15087df7 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -880,8 +880,7 @@ angular.module('OpenSlidesApp.motions.site', [ // Export as a csv file $scope.csvExport = function () { - var element = document.getElementById('downloadLinkCSV'); - MotionCsvExport.export(element, $scope.motionsFiltered); + MotionCsvExport.export($scope.motionsFiltered); }; // Export as docx file $scope.docxExport = function () { @@ -1746,8 +1745,7 @@ angular.module('OpenSlidesApp.motions.site', [ }; // download CSV example file $scope.downloadCSVExample = function () { - var element = document.getElementById('downloadLink'); - MotionCsvExport.downloadExample(element); + MotionCsvExport.downloadExample(); }; } ]) diff --git a/openslides/topics/static/js/topics/csv.js b/openslides/topics/static/js/topics/csv.js index 6d552f5f3..cd5f6c01c 100644 --- a/openslides/topics/static/js/topics/csv.js +++ b/openslides/topics/static/js/topics/csv.js @@ -15,7 +15,7 @@ angular.module('OpenSlidesApp.topics.csv', []) }); }; return { - downloadExample: function (element) { + downloadExample: function () { var csvRows = [makeHeaderline(), // example entries ['Demo 1', 'Demo text 1', '1:00', 'test comment', ''], @@ -23,7 +23,7 @@ angular.module('OpenSlidesApp.topics.csv', []) ['Demo 2', 'Demo text 2', '1:30', '', ''] ]; - CsvDownload(csvRows, element, 'agenda-example.csv'); + CsvDownload(csvRows, 'agenda-example.csv'); }, }; } diff --git a/openslides/topics/static/js/topics/site.js b/openslides/topics/static/js/topics/site.js index 9106e12ba..440d0238c 100644 --- a/openslides/topics/static/js/topics/site.js +++ b/openslides/topics/static/js/topics/site.js @@ -372,8 +372,7 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides }; // download CSV example file $scope.downloadCSVExample = function () { - var element = document.getElementById('downloadLink'); - TopicsCsvExample.downloadExample(element); + TopicsCsvExample.downloadExample(); }; } ]); diff --git a/openslides/users/static/js/users/csv.js b/openslides/users/static/js/users/csv.js index 9d12ad936..7b295272b 100644 --- a/openslides/users/static/js/users/csv.js +++ b/openslides/users/static/js/users/csv.js @@ -17,7 +17,7 @@ angular.module('OpenSlidesApp.users.csv', []) }); }; return { - export: function (element, users) { + export: function (users) { var csvRows = [ makeHeaderline() ]; @@ -38,10 +38,10 @@ angular.module('OpenSlidesApp.users.csv', []) row.push(user.is_committee ? '1' : '0'); csvRows.push(row); }); - CsvDownload(csvRows, element, 'users-export.csv'); + CsvDownload(csvRows, 'users-export.csv'); }, - downloadExample: function (element) { + downloadExample: function () { // try to get an example with two groups and one with one group var groups = Group.getAll(); var csvGroups = ''; @@ -62,7 +62,7 @@ angular.module('OpenSlidesApp.users.csv', []) ['', '', 'Executive Board', '', '', '', '', '', '', '1'], ]; - CsvDownload(csvRows, element, 'users-example.csv'); + CsvDownload(csvRows, 'users-example.csv'); } }; } diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 8b89c4c36..10dd09349 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -636,8 +636,7 @@ angular.module('OpenSlidesApp.users.site', [ }; // Export as a csv file $scope.csvExport = function () { - var element = document.getElementById('downloadLinkCSV'); - UserCsvExport.export(element, $scope.usersFiltered); + UserCsvExport.export($scope.usersFiltered); }; } ]) @@ -1086,8 +1085,7 @@ angular.module('OpenSlidesApp.users.site', [ }; // download CSV example file $scope.downloadCSVExample = function () { - var element = document.getElementById('downloadLink'); - UserCsvExport.downloadExample(element); + UserCsvExport.downloadExample(); }; } ])