mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Merge file and mime code into simple Common/File.js
This commit is contained in:
parent
cb2048f163
commit
9992b20163
9 changed files with 345 additions and 452 deletions
22
README.md
22
README.md
|
|
@ -106,22 +106,22 @@ RainLoop 1.14 vs SnappyMail
|
||||||
|
|
||||||
|js/* |RainLoop |Snappy |
|
|js/* |RainLoop |Snappy |
|
||||||
|----------- |--------: |--------: |
|
|----------- |--------: |--------: |
|
||||||
|admin.js |2.130.942 | 825.627 |
|
|admin.js |2.130.942 | 798.252 |
|
||||||
|app.js |4.184.455 |2.463.325 |
|
|app.js |4.184.455 |2.450.286 |
|
||||||
|boot.js | 671.522 | 5.285 |
|
|boot.js | 671.522 | 5.285 |
|
||||||
|libs.js | 647.614 | 253.356 |
|
|libs.js | 647.614 | 254.910 |
|
||||||
|polyfills.js | 325.834 | 0 |
|
|polyfills.js | 325.834 | 0 |
|
||||||
|TOTAL |7.960.367 |3.547.593 |
|
|TOTAL |7.960.367 |3.508.733 |
|
||||||
|
|
||||||
|js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli |
|
|js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli |
|
||||||
|--------------- |--------: |--------: |--------: |--------: |--------: |
|
|--------------- |--------: |--------: |--------: |--------: |--------: |
|
||||||
|admin.min.js | 252.147 | 112.115 | 73.657 | 30.610 | 26.437 |
|
|admin.min.js | 252.147 | 106.871 | 73.657 | 28.834 | 25.063 |
|
||||||
|app.min.js | 511.202 | 334.897 |140.462 | 86.030 | 69.713 |
|
|app.min.js | 511.202 | 332.055 |140.462 | 85.613 | 69.336 |
|
||||||
|boot.min.js | 66.007 | 2.935 | 22.567 | 1.510 | 1.285 |
|
|boot.min.js | 66.007 | 2.935 | 22.567 | 1.510 | 1.285 |
|
||||||
|libs.min.js | 572.545 | 149.819 |176.720 | 52.834 | 46.924 |
|
|libs.min.js | 572.545 | 149.580 |176.720 | 52.695 | 46.823 |
|
||||||
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|
||||||
|TOTAL |1.434.353 | 599.766 |424.718 |170.984 |144.359 |
|
|TOTAL |1.434.353 | 591.441 |424.718 |168.652 |142.507 |
|
||||||
|TOTAL (no admin) |1.182.206 | 487.651 |351.061 |140.374 |117.922 |
|
|TOTAL (no admin) |1.182.206 | 484.570 |351.061 |139.818 |117.444 |
|
||||||
|
|
||||||
For a user its around 60% smaller and faster than traditional RainLoop.
|
For a user its around 60% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|
|
@ -148,8 +148,8 @@ For a user its around 60% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
||||||
|-------------- |-------: |-------: |------: |------: |------: |
|
|-------------- |-------: |-------: |------: |------: |------: |
|
||||||
|app.css | 340.334 | 186.431 | 46,959 | 28.748 | 24.209 |
|
|app.css | 340.334 | 186.119 | 46,959 | 28.731 | 24.189 |
|
||||||
|app.min.css | 274.791 | 152.540 | 39.618 | 25.289 | 21.842 |
|
|app.min.css | 274.791 | 152.283 | 39.618 | 25.276 | 21.801 |
|
||||||
|boot.css | | 2.534 | | 837 | 668 |
|
|boot.css | | 2.534 | | 837 | 668 |
|
||||||
|boot.min.css | | 2.055 | | 732 | 560 |
|
|boot.min.css | | 2.055 | | 732 | 560 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,5 @@
|
||||||
/* eslint quote-props: 0 */
|
/* eslint quote-props: 0 */
|
||||||
|
|
||||||
/**
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export const FileType = {
|
|
||||||
Unknown: 'unknown',
|
|
||||||
Text: 'text',
|
|
||||||
Html: 'html',
|
|
||||||
Code: 'code',
|
|
||||||
Eml: 'eml',
|
|
||||||
WordText: 'word-text',
|
|
||||||
Pdf: 'pdf',
|
|
||||||
Image: 'image',
|
|
||||||
Audio: 'audio',
|
|
||||||
Video: 'video',
|
|
||||||
Sheet: 'sheet',
|
|
||||||
Presentation: 'presentation',
|
|
||||||
Certificate: 'certificate',
|
|
||||||
CertificateBin: 'certificate-bin',
|
|
||||||
Archive: 'archive',
|
|
||||||
|
|
||||||
getIconClass: function(type) {
|
|
||||||
let result = ['icon-file', ''];
|
|
||||||
switch (type) {
|
|
||||||
case this.Text:
|
|
||||||
case this.Eml:
|
|
||||||
case this.WordText:
|
|
||||||
result[0] += '-text';
|
|
||||||
break;
|
|
||||||
case this.Html:
|
|
||||||
case this.Code:
|
|
||||||
result[0] += '-code';
|
|
||||||
break;
|
|
||||||
case this.Image:
|
|
||||||
result[0] += '-image';
|
|
||||||
break;
|
|
||||||
case this.Audio:
|
|
||||||
result[0] += '-music';
|
|
||||||
break;
|
|
||||||
case this.Video:
|
|
||||||
result[0] += '-movie';
|
|
||||||
break;
|
|
||||||
case this.Archive:
|
|
||||||
result[0] += '-zip';
|
|
||||||
break;
|
|
||||||
case this.Certificate:
|
|
||||||
case this.CertificateBin:
|
|
||||||
result[0] += '-certificate';
|
|
||||||
break;
|
|
||||||
case this.Sheet:
|
|
||||||
result[0] += '-excel';
|
|
||||||
break;
|
|
||||||
case this.Presentation:
|
|
||||||
result[0] += '-chart-graph';
|
|
||||||
break;
|
|
||||||
case this.Pdf:
|
|
||||||
result['icon-none', 'pdf'];
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
313
dev/Common/File.js
Normal file
313
dev/Common/File.js
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
/* eslint key-spacing: 0 */
|
||||||
|
/* eslint quote-props: 0 */
|
||||||
|
|
||||||
|
const
|
||||||
|
cache = {},
|
||||||
|
app = 'application/',
|
||||||
|
msOffice = app+'vnd.openxmlformats-officedocument.',
|
||||||
|
openDoc = app+'vnd.oasis.opendocument.',
|
||||||
|
font = app+'x-font-',
|
||||||
|
|
||||||
|
exts = {
|
||||||
|
'eml': 'message/rfc822',
|
||||||
|
'mime': 'message/rfc822',
|
||||||
|
'rtx': 'text/richtext',
|
||||||
|
'vcard': 'text/vcard',
|
||||||
|
'vcf': 'text/vcard',
|
||||||
|
'htm': 'text/html',
|
||||||
|
'html': 'text/html',
|
||||||
|
'csv': 'text/csv',
|
||||||
|
'ics': 'text/calendar',
|
||||||
|
'ifb': 'text/calendar',
|
||||||
|
'xml': 'text/xml',
|
||||||
|
'json': app+'json',
|
||||||
|
'p10': app+'pkcs10',
|
||||||
|
'p7c': app+'pkcs7-mime',
|
||||||
|
'p7m': app+'pkcs7-mime',
|
||||||
|
'p7s': app+'pkcs7-signature',
|
||||||
|
'torrent': app+'x-bittorrent',
|
||||||
|
|
||||||
|
// scripts
|
||||||
|
'js': app+'javascript',
|
||||||
|
'pl': 'text/perl',
|
||||||
|
'css': 'text/css',
|
||||||
|
'asp': 'text/asp',
|
||||||
|
'php': app+'x-httpd-php',
|
||||||
|
'phtml': app+'x-httpd-php',
|
||||||
|
|
||||||
|
// images
|
||||||
|
'jpg': 'image/jpeg',
|
||||||
|
'ico': 'image/x-icon',
|
||||||
|
'tif': 'image/tiff',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
'svgz': 'image/svg+xml',
|
||||||
|
|
||||||
|
// archives
|
||||||
|
'zip': app+'zip',
|
||||||
|
'7z': app+'x-7z-compressed',
|
||||||
|
'rar': app+'x-rar-compressed',
|
||||||
|
'cab': app+'vnd.ms-cab-compressed',
|
||||||
|
'gz': app+'x-gzip',
|
||||||
|
'tgz': app+'x-gzip',
|
||||||
|
'bz': app+'x-bzip',
|
||||||
|
'bz2': app+'x-bzip2',
|
||||||
|
'deb': app+'x-debian-package',
|
||||||
|
|
||||||
|
// fonts
|
||||||
|
'psf': font+'linux-psf',
|
||||||
|
'ttc': font+'ttf',
|
||||||
|
|
||||||
|
// audio
|
||||||
|
'mp3': 'audio/mpeg',
|
||||||
|
'wav': 'audio/x-wav',
|
||||||
|
'mp4a': 'audio/mp4',
|
||||||
|
'weba': 'audio/webm',
|
||||||
|
'm3u': 'audio/x-mpegurl',
|
||||||
|
|
||||||
|
// video
|
||||||
|
'qt': 'video/quicktime',
|
||||||
|
'mov': 'video/quicktime',
|
||||||
|
'wmv': 'video/windows-media',
|
||||||
|
'avi': 'video/x-msvideo',
|
||||||
|
'3gp': 'video/3gpp',
|
||||||
|
'3g2': 'video/3gpp2',
|
||||||
|
'mp4v': 'video/mp4',
|
||||||
|
'mpg4': 'video/mp4',
|
||||||
|
'ogv': 'video/ogg',
|
||||||
|
'm4v': 'video/x-m4v',
|
||||||
|
'asf': 'video/x-ms-asf',
|
||||||
|
'asx': 'video/x-ms-asf',
|
||||||
|
'wm': 'video/x-ms-wm',
|
||||||
|
'wmx': 'video/x-ms-wmx',
|
||||||
|
'wvx': 'video/x-ms-wvx',
|
||||||
|
'movie': 'video/x-sgi-movie',
|
||||||
|
|
||||||
|
// adobe
|
||||||
|
'pdf': app+'pdf',
|
||||||
|
'psd': 'image/vnd.adobe.photoshop',
|
||||||
|
'ai': app+'postscript',
|
||||||
|
'eps': app+'postscript',
|
||||||
|
'ps': app+'postscript',
|
||||||
|
|
||||||
|
// ms office
|
||||||
|
'doc': app+'msword',
|
||||||
|
'rtf': app+'rtf',
|
||||||
|
'xls': app+'vnd.ms-excel',
|
||||||
|
'ppt': app+'vnd.ms-powerpoint',
|
||||||
|
'docx': msOffice+'wordprocessingml.document',
|
||||||
|
'xlsx': msOffice+'spreadsheetml.sheet',
|
||||||
|
'dotx': msOffice+'wordprocessingml.template',
|
||||||
|
'pptx': msOffice+'presentationml.presentation',
|
||||||
|
|
||||||
|
// open office
|
||||||
|
'odt': openDoc+'text',
|
||||||
|
'ods': openDoc+'spreadsheet',
|
||||||
|
'odp': openDoc+'presentation'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FileType = {
|
||||||
|
Unknown: 'unknown',
|
||||||
|
Text: 'text',
|
||||||
|
Html: 'html',
|
||||||
|
Code: 'code',
|
||||||
|
Eml: 'eml',
|
||||||
|
WordText: 'word-text',
|
||||||
|
Pdf: 'pdf',
|
||||||
|
Image: 'image',
|
||||||
|
Audio: 'audio',
|
||||||
|
Video: 'video',
|
||||||
|
Sheet: 'sheet',
|
||||||
|
Presentation: 'presentation',
|
||||||
|
Certificate: 'certificate',
|
||||||
|
CertificateBin: 'certificate-bin',
|
||||||
|
Archive: 'archive'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const File = {
|
||||||
|
/**
|
||||||
|
* @param {string} fileName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getExtension: fileName => {
|
||||||
|
fileName = fileName.toLowerCase().trim();
|
||||||
|
const result = fileName.split('.').pop();
|
||||||
|
return result === fileName ? '' : result;
|
||||||
|
},
|
||||||
|
|
||||||
|
getContentType: fileName => {
|
||||||
|
fileName = fileName.toLowerCase().trim();
|
||||||
|
if ('winmail.dat' === fileName) {
|
||||||
|
return 'application/ms-tnef';
|
||||||
|
}
|
||||||
|
let ext = fileName.split('.').pop();
|
||||||
|
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf|asc)$/.test(ext))
|
||||||
|
return 'text/plain';
|
||||||
|
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
|
||||||
|
return 'video/mpeg';
|
||||||
|
if (/^aif[cf]?$/.test(ext))
|
||||||
|
return 'audio/aiff';
|
||||||
|
if (/^(aac|flac|midi|ogg)$/.test(ext))
|
||||||
|
return 'audio/'+ext;
|
||||||
|
if (/^(h26[134]|jpgv|mp4|webm)$/.test(ext))
|
||||||
|
return 'video/'+ext;
|
||||||
|
if (/^(otf|pcf|snf|ttf)$/.test(ext))
|
||||||
|
return font+ext;
|
||||||
|
if (/^(png|jpeg|gif|tiff|webp)$/.test(ext))
|
||||||
|
return 'image/'+ext;
|
||||||
|
|
||||||
|
return exts[ext] || app+'octet-stream';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} sExt
|
||||||
|
* @param {string} sMimeType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getType: (ext, mimeType) => {
|
||||||
|
ext = ext.toLowerCase().trim();
|
||||||
|
mimeType = mimeType.toLowerCase().trim().replace('csv/plain', 'text/csv');
|
||||||
|
|
||||||
|
let key = ext + mimeType;
|
||||||
|
if (cache[key]) {
|
||||||
|
return cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = FileType.Unknown;
|
||||||
|
const mimeTypeParts = mimeType.split('/'),
|
||||||
|
type = mimeTypeParts[1],
|
||||||
|
match = str => type.includes(str);
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case 'image' === mimeTypeParts[0] || ['png', 'jpg', 'jpeg', 'gif'].includes(ext):
|
||||||
|
result = FileType.Image;
|
||||||
|
break;
|
||||||
|
case 'audio' === mimeTypeParts[0] || ['mp3', 'ogg', 'oga', 'wav'].includes(ext):
|
||||||
|
result = FileType.Audio;
|
||||||
|
break;
|
||||||
|
case 'video' === mimeTypeParts[0] || ['mkv', 'avi'].includes(ext):
|
||||||
|
result = FileType.Video;
|
||||||
|
break;
|
||||||
|
case ['php', 'js', 'css'].includes(ext):
|
||||||
|
result = FileType.Code;
|
||||||
|
break;
|
||||||
|
case 'eml' === ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
|
||||||
|
result = FileType.Eml;
|
||||||
|
break;
|
||||||
|
case 'text/html' === mimeType || ['html'].includes(ext):
|
||||||
|
result = FileType.Html;
|
||||||
|
break;
|
||||||
|
case 'text' === mimeTypeParts[0] || ['txt', 'log'].includes(ext):
|
||||||
|
result = FileType.Text;
|
||||||
|
break;
|
||||||
|
case [
|
||||||
|
'zip',
|
||||||
|
'7z',
|
||||||
|
'tar',
|
||||||
|
'rar',
|
||||||
|
'gzip',
|
||||||
|
'bzip',
|
||||||
|
'bzip2',
|
||||||
|
'x-zip',
|
||||||
|
'x-7z',
|
||||||
|
'x-rar',
|
||||||
|
'x-tar',
|
||||||
|
'x-gzip',
|
||||||
|
'x-bzip',
|
||||||
|
'x-bzip2',
|
||||||
|
'x-zip-compressed',
|
||||||
|
'x-7z-compressed',
|
||||||
|
'x-rar-compressed'
|
||||||
|
].includes(type) || ['zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2'].includes(ext):
|
||||||
|
result = FileType.Archive;
|
||||||
|
break;
|
||||||
|
case ['pdf', 'x-pdf'].includes(type) || ['pdf'].includes(ext):
|
||||||
|
result = FileType.Pdf;
|
||||||
|
break;
|
||||||
|
case ['application/pgp-signature', 'application/pgp-keys'].includes(mimeType) ||
|
||||||
|
['asc', 'pem', 'ppk'].includes(ext):
|
||||||
|
result = FileType.Certificate;
|
||||||
|
break;
|
||||||
|
case ['application/pkcs7-signature'].includes(mimeType) || ['p7s'].includes(ext):
|
||||||
|
result = FileType.CertificateBin;
|
||||||
|
break;
|
||||||
|
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|
||||||
|
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
|
||||||
|
result = FileType.WordText;
|
||||||
|
break;
|
||||||
|
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
|
||||||
|
result = FileType.Sheet;
|
||||||
|
break;
|
||||||
|
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
|
||||||
|
result = FileType.Presentation;
|
||||||
|
break;
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
return cache[key] = result;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} sFileType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getTypeIconClass: fileType => {
|
||||||
|
let result = ['icon-file', ''];
|
||||||
|
switch (fileType) {
|
||||||
|
case FileType.Text:
|
||||||
|
case FileType.Eml:
|
||||||
|
case FileType.WordText:
|
||||||
|
result[0] += '-text';
|
||||||
|
break;
|
||||||
|
case FileType.Html:
|
||||||
|
case FileType.Code:
|
||||||
|
result[0] += '-code';
|
||||||
|
break;
|
||||||
|
case FileType.Image:
|
||||||
|
result[0] += '-image';
|
||||||
|
break;
|
||||||
|
case FileType.Audio:
|
||||||
|
result[0] += '-music';
|
||||||
|
break;
|
||||||
|
case FileType.Video:
|
||||||
|
result[0] += '-movie';
|
||||||
|
break;
|
||||||
|
case FileType.Archive:
|
||||||
|
result[0] += '-zip';
|
||||||
|
break;
|
||||||
|
case FileType.Certificate:
|
||||||
|
case FileType.CertificateBin:
|
||||||
|
result[0] += '-certificate';
|
||||||
|
break;
|
||||||
|
case FileType.Sheet:
|
||||||
|
result[0] += '-excel';
|
||||||
|
break;
|
||||||
|
case FileType.Presentation:
|
||||||
|
result[0] += '-chart-graph';
|
||||||
|
break;
|
||||||
|
case FileType.Pdf:
|
||||||
|
result['icon-none', 'pdf'];
|
||||||
|
break;
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
getIconClass: (ext, mime) => File.getTypeIconClass(File.getType(ext, mime)),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} sFileType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getCombinedIconClass: data => {
|
||||||
|
if (Array.isNotEmpty(data)) {
|
||||||
|
let icons = data
|
||||||
|
.map(item => item ? File.getIconClass(File.getExtension(item[0]), item[1])[0] : '')
|
||||||
|
.filter((value, index, self) => value && self.indexOf(value) == index);
|
||||||
|
|
||||||
|
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
|
||||||
|
? icons[0]
|
||||||
|
: 'icon-attachment';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
/* eslint key-spacing: 0 */
|
|
||||||
/* eslint quote-props: 0 */
|
|
||||||
|
|
||||||
const Mime = {
|
|
||||||
'eml': 'message/rfc822',
|
|
||||||
'mime': 'message/rfc822',
|
|
||||||
'txt': 'text/plain',
|
|
||||||
'text': 'text/plain',
|
|
||||||
'def': 'text/plain',
|
|
||||||
'list': 'text/plain',
|
|
||||||
'in': 'text/plain',
|
|
||||||
'ini': 'text/plain',
|
|
||||||
'log': 'text/plain',
|
|
||||||
'sql': 'text/plain',
|
|
||||||
'cfg': 'text/plain',
|
|
||||||
'conf': 'text/plain',
|
|
||||||
'asc': 'text/plain',
|
|
||||||
'rtx': 'text/richtext',
|
|
||||||
'vcard': 'text/vcard',
|
|
||||||
'vcf': 'text/vcard',
|
|
||||||
'htm': 'text/html',
|
|
||||||
'html': 'text/html',
|
|
||||||
'csv': 'text/csv',
|
|
||||||
'ics': 'text/calendar',
|
|
||||||
'ifb': 'text/calendar',
|
|
||||||
'xml': 'text/xml',
|
|
||||||
'json': 'application/json',
|
|
||||||
'swf': 'application/x-shockwave-flash',
|
|
||||||
'hlp': 'application/winhlp',
|
|
||||||
'wgt': 'application/widget',
|
|
||||||
'chm': 'application/vnd.ms-htmlhelp',
|
|
||||||
'p10': 'application/pkcs10',
|
|
||||||
'p7c': 'application/pkcs7-mime',
|
|
||||||
'p7m': 'application/pkcs7-mime',
|
|
||||||
'p7s': 'application/pkcs7-signature',
|
|
||||||
'torrent': 'application/x-bittorrent',
|
|
||||||
|
|
||||||
// scripts
|
|
||||||
'js': 'application/javascript',
|
|
||||||
'pl': 'text/perl',
|
|
||||||
'css': 'text/css',
|
|
||||||
'asp': 'text/asp',
|
|
||||||
'php': 'application/x-httpd-php',
|
|
||||||
'php3': 'application/x-httpd-php',
|
|
||||||
'php4': 'application/x-httpd-php',
|
|
||||||
'php5': 'application/x-httpd-php',
|
|
||||||
'phtml': 'application/x-httpd-php',
|
|
||||||
|
|
||||||
// images
|
|
||||||
'png': 'image/png',
|
|
||||||
'jpg': 'image/jpeg',
|
|
||||||
'jpeg': 'image/jpeg',
|
|
||||||
'jpe': 'image/jpeg',
|
|
||||||
'jfif': 'image/jpeg',
|
|
||||||
'gif': 'image/gif',
|
|
||||||
'bmp': 'image/bmp',
|
|
||||||
'cgm': 'image/cgm',
|
|
||||||
'ief': 'image/ief',
|
|
||||||
'ico': 'image/x-icon',
|
|
||||||
'tif': 'image/tiff',
|
|
||||||
'tiff': 'image/tiff',
|
|
||||||
'svg': 'image/svg+xml',
|
|
||||||
'svgz': 'image/svg+xml',
|
|
||||||
'djv': 'image/vnd.djvu',
|
|
||||||
'djvu': 'image/vnd.djvu',
|
|
||||||
'webp': 'image/webp',
|
|
||||||
|
|
||||||
// archives
|
|
||||||
'zip': 'application/zip',
|
|
||||||
'7z': 'application/x-7z-compressed',
|
|
||||||
'rar': 'application/x-rar-compressed',
|
|
||||||
'exe': 'application/x-msdownload',
|
|
||||||
'dll': 'application/x-msdownload',
|
|
||||||
'scr': 'application/x-msdownload',
|
|
||||||
'com': 'application/x-msdownload',
|
|
||||||
'bat': 'application/x-msdownload',
|
|
||||||
'msi': 'application/x-msdownload',
|
|
||||||
'cab': 'application/vnd.ms-cab-compressed',
|
|
||||||
'gz': 'application/x-gzip',
|
|
||||||
'tgz': 'application/x-gzip',
|
|
||||||
'bz': 'application/x-bzip',
|
|
||||||
'bz2': 'application/x-bzip2',
|
|
||||||
'deb': 'application/x-debian-package',
|
|
||||||
|
|
||||||
// fonts
|
|
||||||
'psf': 'application/x-font-linux-psf',
|
|
||||||
'otf': 'application/x-font-otf',
|
|
||||||
'pcf': 'application/x-font-pcf',
|
|
||||||
'snf': 'application/x-font-snf',
|
|
||||||
'ttf': 'application/x-font-ttf',
|
|
||||||
'ttc': 'application/x-font-ttf',
|
|
||||||
|
|
||||||
// audio
|
|
||||||
'mp3': 'audio/mpeg',
|
|
||||||
'amr': 'audio/amr',
|
|
||||||
'aac': 'audio/x-aac',
|
|
||||||
'aif': 'audio/x-aiff',
|
|
||||||
'aifc': 'audio/x-aiff',
|
|
||||||
'aiff': 'audio/x-aiff',
|
|
||||||
'wav': 'audio/x-wav',
|
|
||||||
'wma': 'audio/x-ms-wma',
|
|
||||||
'wax': 'audio/x-ms-wax',
|
|
||||||
'midi': 'audio/midi',
|
|
||||||
'mp4a': 'audio/mp4',
|
|
||||||
'ogg': 'audio/ogg',
|
|
||||||
'weba': 'audio/webm',
|
|
||||||
'ra': 'audio/x-pn-realaudio',
|
|
||||||
'ram': 'audio/x-pn-realaudio',
|
|
||||||
'rmp': 'audio/x-pn-realaudio-plugin',
|
|
||||||
'm3u': 'audio/x-mpegurl',
|
|
||||||
|
|
||||||
// video
|
|
||||||
'flv': 'video/x-flv',
|
|
||||||
'qt': 'video/quicktime',
|
|
||||||
'mov': 'video/quicktime',
|
|
||||||
'wmv': 'video/windows-media',
|
|
||||||
'avi': 'video/x-msvideo',
|
|
||||||
'mpg': 'video/mpeg',
|
|
||||||
'mpeg': 'video/mpeg',
|
|
||||||
'mpe': 'video/mpeg',
|
|
||||||
'm1v': 'video/mpeg',
|
|
||||||
'm2v': 'video/mpeg',
|
|
||||||
'3gp': 'video/3gpp',
|
|
||||||
'3g2': 'video/3gpp2',
|
|
||||||
'h261': 'video/h261',
|
|
||||||
'h263': 'video/h263',
|
|
||||||
'h264': 'video/h264',
|
|
||||||
'jpgv': 'video/jpgv',
|
|
||||||
'mp4': 'video/mp4',
|
|
||||||
'mp4v': 'video/mp4',
|
|
||||||
'mpg4': 'video/mp4',
|
|
||||||
'ogv': 'video/ogg',
|
|
||||||
'webm': 'video/webm',
|
|
||||||
'm4v': 'video/x-m4v',
|
|
||||||
'asf': 'video/x-ms-asf',
|
|
||||||
'asx': 'video/x-ms-asf',
|
|
||||||
'wm': 'video/x-ms-wm',
|
|
||||||
'wmx': 'video/x-ms-wmx',
|
|
||||||
'wvx': 'video/x-ms-wvx',
|
|
||||||
'movie': 'video/x-sgi-movie',
|
|
||||||
|
|
||||||
// adobe
|
|
||||||
'pdf': 'application/pdf',
|
|
||||||
'psd': 'image/vnd.adobe.photoshop',
|
|
||||||
'ai': 'application/postscript',
|
|
||||||
'eps': 'application/postscript',
|
|
||||||
'ps': 'application/postscript',
|
|
||||||
|
|
||||||
// ms office
|
|
||||||
'doc': 'application/msword',
|
|
||||||
'dot': 'application/msword',
|
|
||||||
'rtf': 'application/rtf',
|
|
||||||
'xls': 'application/vnd.ms-excel',
|
|
||||||
'ppt': 'application/vnd.ms-powerpoint',
|
|
||||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
||||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
||||||
'dotx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
|
||||||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
||||||
|
|
||||||
// open office
|
|
||||||
'odt': 'application/vnd.oasis.opendocument.text',
|
|
||||||
'ods': 'application/vnd.oasis.opendocument.spreadsheet'
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Mime, Mime as default };
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { ComposeType, SaveSettingsStep, FolderType } from 'Common/Enums';
|
import { ComposeType, SaveSettingsStep, FolderType } from 'Common/Enums';
|
||||||
import { Mime } from 'Common/Mime';
|
|
||||||
|
|
||||||
const
|
const
|
||||||
doc = document,
|
doc = document,
|
||||||
|
|
@ -820,39 +819,6 @@ export function computedPagenatorHelper(koCurrentPage, koPageCount) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} fileName
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function getFileExtension(fileName) {
|
|
||||||
fileName = fileName.toLowerCase().trim();
|
|
||||||
|
|
||||||
const result = fileName.split('.').pop();
|
|
||||||
return result === fileName ? '' : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} fileName
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function mimeContentType(fileName) {
|
|
||||||
let ext = '',
|
|
||||||
result = 'application/octet-stream';
|
|
||||||
|
|
||||||
fileName = fileName.toLowerCase().trim();
|
|
||||||
|
|
||||||
if ('winmail.dat' === fileName) {
|
|
||||||
return 'application/ms-tnef';
|
|
||||||
}
|
|
||||||
|
|
||||||
ext = getFileExtension(fileName);
|
|
||||||
if (ext && ext.length && undefined !== Mime[ext]) {
|
|
||||||
result = Mime[ext];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} color
|
* @param {string} color
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { FileType } from 'Common/Enums';
|
import { File, FileType } from 'Common/File';
|
||||||
import { pInt, getFileExtension, friendlySize } from 'Common/Utils';
|
import { pInt, friendlySize } from 'Common/Utils';
|
||||||
import {
|
import {
|
||||||
attachmentDownload,
|
attachmentDownload,
|
||||||
attachmentPreview,
|
attachmentPreview,
|
||||||
|
|
@ -16,159 +16,6 @@ import Audio from 'Common/Audio';
|
||||||
|
|
||||||
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
|
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} sExt
|
|
||||||
* @param {string} sMimeType
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const staticFileType = (() => {
|
|
||||||
let cache = {},
|
|
||||||
msOffice = 'vnd.openxmlformats-officedocument',
|
|
||||||
openDoc = 'vnd.oasis.opendocument';
|
|
||||||
return (ext, mimeType) => {
|
|
||||||
ext = ext.toLowerCase().trim();
|
|
||||||
mimeType = mimeType.toLowerCase().trim();
|
|
||||||
|
|
||||||
let key = ext + mimeType;
|
|
||||||
if (cache[key]) {
|
|
||||||
return cache[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = FileType.Unknown;
|
|
||||||
const mimeTypeParts = mimeType.split('/'),
|
|
||||||
type = mimeTypeParts[1],
|
|
||||||
match = str => type.includes(str);
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case 'image' === mimeTypeParts[0] || ['png', 'jpg', 'jpeg', 'gif'].includes(ext):
|
|
||||||
result = FileType.Image;
|
|
||||||
break;
|
|
||||||
case 'audio' === mimeTypeParts[0] || ['mp3', 'ogg', 'oga', 'wav'].includes(ext):
|
|
||||||
result = FileType.Audio;
|
|
||||||
break;
|
|
||||||
case 'video' === mimeTypeParts[0] || ['mkv', 'avi'].includes(ext):
|
|
||||||
result = FileType.Video;
|
|
||||||
break;
|
|
||||||
case ['php', 'js', 'css'].includes(ext):
|
|
||||||
result = FileType.Code;
|
|
||||||
break;
|
|
||||||
case 'eml' === ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
|
|
||||||
result = FileType.Eml;
|
|
||||||
break;
|
|
||||||
case ('text' === mimeTypeParts[0] && 'html' !== type) || ['txt', 'log'].includes(ext):
|
|
||||||
result = FileType.Text;
|
|
||||||
break;
|
|
||||||
case 'text/html' === mimeType || ['html'].includes(ext):
|
|
||||||
result = FileType.Html;
|
|
||||||
break;
|
|
||||||
case [
|
|
||||||
'zip',
|
|
||||||
'7z',
|
|
||||||
'tar',
|
|
||||||
'rar',
|
|
||||||
'gzip',
|
|
||||||
'bzip',
|
|
||||||
'bzip2',
|
|
||||||
'x-zip',
|
|
||||||
'x-7z',
|
|
||||||
'x-rar',
|
|
||||||
'x-tar',
|
|
||||||
'x-gzip',
|
|
||||||
'x-bzip',
|
|
||||||
'x-bzip2',
|
|
||||||
'x-zip-compressed',
|
|
||||||
'x-7z-compressed',
|
|
||||||
'x-rar-compressed'
|
|
||||||
].includes(type) || ['zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2'].includes(ext):
|
|
||||||
result = FileType.Archive;
|
|
||||||
break;
|
|
||||||
case ['pdf', 'x-pdf'].includes(type) || ['pdf'].includes(ext):
|
|
||||||
result = FileType.Pdf;
|
|
||||||
break;
|
|
||||||
case ['application/pgp-signature', 'application/pgp-keys'].includes(mimeType) ||
|
|
||||||
['asc', 'pem', 'ppk'].includes(ext):
|
|
||||||
result = FileType.Certificate;
|
|
||||||
break;
|
|
||||||
case ['application/pkcs7-signature'].includes(mimeType) || ['p7s'].includes(ext):
|
|
||||||
result = FileType.CertificateBin;
|
|
||||||
break;
|
|
||||||
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|
|
||||||
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
|
|
||||||
result = FileType.WordText;
|
|
||||||
break;
|
|
||||||
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
|
|
||||||
result = FileType.Sheet;
|
|
||||||
break;
|
|
||||||
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
|
|
||||||
result = FileType.Presentation;
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache[key] = result;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} sFileType
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const staticIconClass = fileType => FileType.getIconClass(fileType);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @static
|
|
||||||
* @param {string} sFileType
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const staticCombinedIconClass = (data) => {
|
|
||||||
let result = '',
|
|
||||||
types = [];
|
|
||||||
|
|
||||||
if (Array.isNotEmpty(data)) {
|
|
||||||
result = 'icon-attachment';
|
|
||||||
types = data.map(item => item ? staticFileType(getFileExtension(item[0]), item[1]) : '')
|
|
||||||
.filter((value, index, self) => !!value && self.indexOf(value) == index);
|
|
||||||
|
|
||||||
if (types && 1 === types.length && types[0]) {
|
|
||||||
switch (types[0]) {
|
|
||||||
case FileType.Text:
|
|
||||||
case FileType.WordText:
|
|
||||||
result = 'icon-file-text';
|
|
||||||
break;
|
|
||||||
case FileType.Html:
|
|
||||||
case FileType.Code:
|
|
||||||
result = 'icon-file-code';
|
|
||||||
break;
|
|
||||||
case FileType.Image:
|
|
||||||
result = 'icon-file-image';
|
|
||||||
break;
|
|
||||||
case FileType.Audio:
|
|
||||||
result = 'icon-file-music';
|
|
||||||
break;
|
|
||||||
case FileType.Video:
|
|
||||||
result = 'icon-file-movie';
|
|
||||||
break;
|
|
||||||
case FileType.Archive:
|
|
||||||
result = 'icon-file-zip';
|
|
||||||
break;
|
|
||||||
case FileType.Certificate:
|
|
||||||
case FileType.CertificateBin:
|
|
||||||
result = 'icon-file-certificate';
|
|
||||||
break;
|
|
||||||
case FileType.Sheet:
|
|
||||||
result = 'icon-file-excel';
|
|
||||||
break;
|
|
||||||
case FileType.Presentation:
|
|
||||||
result = 'icon-file-chart-graph';
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AttachmentModel extends AbstractModel {
|
class AttachmentModel extends AbstractModel {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('AttachmentModel');
|
super('AttachmentModel');
|
||||||
|
|
@ -232,8 +79,8 @@ class AttachmentModel extends AbstractModel {
|
||||||
this.friendlySize = friendlySize(this.estimatedSize);
|
this.friendlySize = friendlySize(this.estimatedSize);
|
||||||
this.cidWithoutTags = this.cid.replace(/^<+/, '').replace(/>+$/, '');
|
this.cidWithoutTags = this.cid.replace(/^<+/, '').replace(/>+$/, '');
|
||||||
|
|
||||||
this.fileNameExt = getFileExtension(this.fileName);
|
this.fileNameExt = File.getExtension(this.fileName);
|
||||||
this.fileType = staticFileType(this.fileNameExt, this.mimeType);
|
this.fileType = File.getType(this.fileNameExt, this.mimeType);
|
||||||
|
|
||||||
bResult = true;
|
bResult = true;
|
||||||
}
|
}
|
||||||
|
|
@ -406,14 +253,14 @@ class AttachmentModel extends AbstractModel {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return staticIconClass(this.fileType)[0];
|
return File.getTypeIconClass(this.fileType)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconText() {
|
iconText() {
|
||||||
return staticIconClass(this.fileType)[1];
|
return File.getTypeIconClass(this.fileType)[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import { pInt, friendlySize, mimeContentType, getFileExtension } from 'Common/Utils';
|
import { pInt, friendlySize } from 'Common/Utils';
|
||||||
|
import { File } from 'Common/File';
|
||||||
|
|
||||||
import { staticIconClass, staticFileType } from 'Model/Attachment';
|
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
class ComposeAttachmentModel extends AbstractModel {
|
class ComposeAttachmentModel extends AbstractModel {
|
||||||
|
|
@ -54,8 +54,8 @@ class ComposeAttachmentModel extends AbstractModel {
|
||||||
return null === localSize ? '' : friendlySize(localSize);
|
return null === localSize ? '' : friendlySize(localSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mimeType = ko.computed(() => mimeContentType(this.fileName()));
|
this.mimeType = ko.computed(() => File.getContentType(this.fileName()));
|
||||||
this.fileExt = ko.computed(() => getFileExtension(this.fileName()));
|
this.fileExt = ko.computed(() => File.getExtension(this.fileName()));
|
||||||
|
|
||||||
this.regDisposables([
|
this.regDisposables([
|
||||||
this.progressText,
|
this.progressText,
|
||||||
|
|
@ -104,14 +104,14 @@ class ComposeAttachmentModel extends AbstractModel {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return staticIconClass(staticFileType(this.fileExt(), this.mimeType()))[0];
|
return File.getIconClass(this.fileExt(), this.mimeType())[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconText() {
|
iconText() {
|
||||||
return staticIconClass(staticFileType(this.fileExt(), this.mimeType()))[1];
|
return File.getIconClass(this.fileExt(), this.mimeType())[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { messageViewLink, messageDownloadLink } from 'Common/Links';
|
||||||
import FolderStore from 'Stores/User/Folder';
|
import FolderStore from 'Stores/User/Folder';
|
||||||
import PgpStore from 'Stores/User/Pgp';
|
import PgpStore from 'Stores/User/Pgp';
|
||||||
|
|
||||||
import { staticCombinedIconClass } from 'Model/Attachment';
|
import { File } from 'Common/File';
|
||||||
import { AttachmentCollectionModel } from 'Model/AttachmentCollection';
|
import { AttachmentCollectionModel } from 'Model/AttachmentCollection';
|
||||||
import { EmailCollectionModel } from 'Model/EmailCollection';
|
import { EmailCollectionModel } from 'Model/EmailCollection';
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
@ -69,7 +69,7 @@ class MessageModel extends AbstractModel {
|
||||||
this.attachmentsSpecData = ko.observableArray([]);
|
this.attachmentsSpecData = ko.observableArray([]);
|
||||||
|
|
||||||
this.attachmentIconClass = ko.computed(() =>
|
this.attachmentIconClass = ko.computed(() =>
|
||||||
staticCombinedIconClass(this.hasAttachments() ? this.attachmentsSpecData() : [])
|
File.getCombinedIconClass(this.hasAttachments() ? this.attachmentsSpecData() : [])
|
||||||
);
|
);
|
||||||
|
|
||||||
this.isHtml = ko.observable(false);
|
this.isHtml = ko.observable(false);
|
||||||
|
|
|
||||||
|
|
@ -828,26 +828,20 @@ END;
|
||||||
'ttc' => 'application/x-font-ttf',
|
'ttc' => 'application/x-font-ttf',
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
|
'aac' => 'audio/aac',
|
||||||
|
'flac' => 'audio/flac',
|
||||||
'mp3' => 'audio/mpeg',
|
'mp3' => 'audio/mpeg',
|
||||||
'amr' => 'audio/amr',
|
'aif' => 'audio/aiff',
|
||||||
'aac' => 'audio/x-aac',
|
'aifc' => 'audio/aiff',
|
||||||
'aif' => 'audio/x-aiff',
|
'aiff' => 'audio/aiff',
|
||||||
'aifc' => 'audio/x-aiff',
|
|
||||||
'aiff' => 'audio/x-aiff',
|
|
||||||
'wav' => 'audio/x-wav',
|
'wav' => 'audio/x-wav',
|
||||||
'wma' => 'audio/x-ms-wma',
|
|
||||||
'wax' => 'audio/x-ms-wax',
|
|
||||||
'midi' => 'audio/midi',
|
'midi' => 'audio/midi',
|
||||||
'mp4a' => 'audio/mp4',
|
'mp4a' => 'audio/mp4',
|
||||||
'ogg' => 'audio/ogg',
|
'ogg' => 'audio/ogg',
|
||||||
'weba' => 'audio/webm',
|
'weba' => 'audio/webm',
|
||||||
'ra' => 'audio/x-pn-realaudio',
|
|
||||||
'ram' => 'audio/x-pn-realaudio',
|
|
||||||
'rmp' => 'audio/x-pn-realaudio-plugin',
|
|
||||||
'm3u' => 'audio/x-mpegurl',
|
'm3u' => 'audio/x-mpegurl',
|
||||||
|
|
||||||
// video
|
// video
|
||||||
'flv' => 'video/x-flv',
|
|
||||||
'qt' => 'video/quicktime',
|
'qt' => 'video/quicktime',
|
||||||
'mov' => 'video/quicktime',
|
'mov' => 'video/quicktime',
|
||||||
'avi' => 'video/x-msvideo',
|
'avi' => 'video/x-msvideo',
|
||||||
|
|
@ -896,7 +890,8 @@ END;
|
||||||
|
|
||||||
// open office
|
// open office
|
||||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet'
|
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
'odp' => 'application/vnd.oasis.opendocument.presentation'
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue