import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableOnlyoffice, enableSeadoc, enableFileTags } from './constants'; import TextTranslation from './text-translation'; import React from 'react'; import toaster from '../components/toast'; import PermissionDeniedTip from '../components/permission-denied-tip'; import { compareTwoString } from './compare-two-string'; import { PRIVATE_FILE_TYPE } from '../constants'; export const Utils = { keyCodes: { enter: 13, esc: 27, space: 32, tab: 9, up: 38, down: 40 }, bytesToSize: function (bytes) { if (typeof(bytes) == 'undefined') return ' '; if (bytes < 0) return '--'; const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; if (bytes === 0) return bytes + ' ' + sizes[0]; const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10); if (i === 0) return bytes + ' ' + sizes[i]; return (bytes / (1000 ** i)).toFixed(1) + ' ' + sizes[i]; }, isHiDPI: function () { var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1; if (pixelRatio > 1) { return true; } else { return false; } }, isDesktop: function () { return window.innerWidth >= 768; }, isWeChat: function () { let ua = window.navigator.userAgent.toLowerCase(); let isWeChat = ua.match(/MicroMessenger/i) == 'micromessenger'; let isEnterpriseWeChat = ua.match(/MicroMessenger/i) == 'micromessenger' && ua.match(/wxwork/i) == 'wxwork'; return isEnterpriseWeChat || isWeChat; }, FILEEXT_ICON_MAP: { // text file 'txt': 'txt.png', // markdown file 'md': 'md.png', // pdf file 'pdf': 'pdf.png', // document file 'doc': 'word.png', 'docx': 'word.png', 'odt': 'word.png', 'fodt': 'word.png', 'ppt': 'ppt.png', 'pptx': 'ppt.png', 'odp': 'ppt.png', 'fodp': 'ppt.png', 'xls': 'excel.png', 'xlsx': 'excel.png', 'ods': 'excel.png', 'fods': 'excel.png', // video 'mp4': 'video.png', 'ogv': 'video.png', 'webm': 'video.png', 'mov': 'video.png', 'flv': 'video.png', 'wmv': 'video.png', 'rmvb': 'video.png', // music file 'mp3': 'music.png', 'oga': 'music.png', 'ogg': 'music.png', 'wav': 'music.png', 'flac': 'music.png', 'opus': 'music.png', 'aac': 'music.png', 'ac3': 'music.png', 'wma': 'music.png', // image file 'jpg': 'pic.png', 'jpeg': 'pic.png', 'png': 'pic.png', 'svg': 'pic.png', 'gif': 'pic.png', 'bmp': 'pic.png', 'ico': 'pic.png', 'heic': 'pic.png', // photoshop file 'psd': 'psd.png', // zip file 'zip': 'zip.png', 'rar': 'zip.png', 'tar': 'zip.png', // style file 'css': 'css.png', // sdoc file 'sdoc': 'sdoc.png', 'sdoc_notification': 'sdoc_notification.ico', // default 'default': 'file.png', }, // check if a file is an image imageCheck: function (filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; } var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); var image_exts = ['gif', 'jpeg', 'jpg', 'png', 'ico', 'bmp', 'tif', 'tiff', 'jfif', 'heic']; if (image_exts.indexOf(file_ext) != -1) { return true; } else { return false; } }, pdfCheck: function (filename) { if (filename.lastIndexOf('.') == -1) { return false; } var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); var image_exts = ['pdf']; if (image_exts.indexOf(file_ext) != -1) { return true; } else { return false; } }, getShareLinkPermissionList: function (itemType, permission, path, canEdit) { // itemType: library, dir, file // permission: rw, r, admin, cloud-edit, preview, custom-* let permissionOptions = []; const { isCustomPermission } = Utils.getUserPermission(permission); if (isCustomPermission) { permissionOptions.push('preview_download'); permissionOptions.push('preview_only'); return permissionOptions; } if (permission == 'rw' || permission == 'admin' || permission == 'r') { permissionOptions.push('preview_download'); } permissionOptions.push('preview_only'); if (itemType == 'library' || itemType == 'dir') { if (permission == 'rw' || permission == 'admin') { permissionOptions.push('download_upload'); } } else { if (this.isEditableOfficeFile(path) && (permission == 'rw' || permission == 'admin') && canEdit) { permissionOptions.push('edit_download'); } // not support // if (this.isEditableOfficeFile(path) && (permission == 'cloud-edit')) { // permissionOptions.push('cloud_edit'); // } } return permissionOptions; }, getShareLinkPermissionStr: function (permissions) { const { can_edit, can_download, can_upload } = permissions; switch (`${can_edit} ${can_download} ${can_upload}`) { case 'false true false': return 'preview_download'; case 'false false false': return 'preview_only'; case 'false true true': return 'download_upload'; case 'true true false': return 'edit_download'; case 'true false false': return 'cloud_edit'; } }, isEditableOfficeFile: function (filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; } var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); var exts = ['docx', 'pptx', 'xlsx']; if (exts.indexOf(file_ext) != -1) { return true; } else { return false; } }, // check if a file is a video videoCheck: function (filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; } var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); var exts = ['mp4', 'ogv', 'webm', 'mov']; if (exts.indexOf(file_ext) != -1) { return true; } else { return false; } }, checkDuplicatedNameInList: function (list, targetName) { return list.some(object => { return object.name === targetName; }); }, encodePath: function (path) { // IE8 does not support 'map()' /* return path.split('/').map(function(e) { return encodeURIComponent(e); }).join('/'); */ if (!path) { return ''; } var path_arr = path.split('/'); var path_arr_ = []; for (var i = 0, len = path_arr.length; i < len; i++) { path_arr_.push(encodeURIComponent(path_arr[i])); } return path_arr_.join('/'); }, HTMLescape: function (html) { return document.createElement('div') .appendChild(document.createTextNode(html)) .parentNode .innerHTML; }, generateDialogTitle: function (title, operationTarget) { /* * @param title: gettext('...{placeholder}...') */ /* const targetStr = this.HTMLescape(operationTarget); const str = `${targetStr}`; return title.replace('{placeholder}', str); */ return title.replace('{placeholder}', operationTarget); }, getFileName: function (filePath) { let lastIndex = filePath.lastIndexOf('/'); return filePath.slice(lastIndex + 1); }, /** * input: '/abc/bc/cb' * output: ['/abc', '/abc/bc', '/abc/bc/cb']; */ getPaths: function (path) { let paths = path.split('/').slice(1); let result = []; while (paths.length) { result.push('/' + paths.join('/')); paths.pop(); } return result.reverse(); }, /** * input: * eg: / * ../abc/abc/ * ../abc/bc * output(return): * eg: / * abc * bc */ getFolderName: function (path) { if (path === '/') { return path; } path = path[path.length - 1] !== '/' ? path : path.slice(0, path.length - 1); return path.slice(path.lastIndexOf('/') + 1); }, /* return dirname of a path. if path is '/', return '/'. */ getDirName: function (path) { let dir = path.slice(0, path.lastIndexOf('/')); if (dir === '') { return '/'; } else { return dir; } }, isChildPath: function (child, parent) { let p = this.getDirName(child); return p === parent; }, isAncestorPath: function (ancestor, path) { return path.indexOf(ancestor) > -1; }, renameAncestorPath: function (path, ancestor, newAncestor) { return path.replace(ancestor, newAncestor); }, joinPath: function (pathA, pathB) { if (pathA[pathA.length - 1] === '/') { return pathA + pathB; } else { return pathA + '/' + pathB; } }, isSupportUploadFolder: function () { return navigator.userAgent.indexOf('Firefox') != -1 || navigator.userAgent.indexOf('Chrome') > -1 || navigator.userAgent.indexOf('Safari') > -1; }, isIEBrowser: function () { // is ie <= ie11 not include Edge var userAgent = navigator.userAgent; var isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1; var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1; return isIE || isIE11; }, getDefaultLibIconUrl: function (isBig) { let size = Utils.isHiDPI() ? 48 : 24; size = isBig ? 256 : size; let icon_name = 'lib.png'; return mediaUrl + 'img/lib/' + size + '/' + icon_name; }, getLibIconUrl: function (repo, isBig) { let permission = repo.permission || repo.share_permission; // Compatible with regular repo and repo shared let size = Utils.isHiDPI() ? 48 : 24; size = isBig ? 256 : size; let icon_name = 'lib.png'; if (repo.encrypted) { icon_name = 'lib-encrypted.png'; } switch (permission) { case 'r': icon_name = 'lib-readonly.png'; break; case 'preview': icon_name = 'lib-cloud-preview.png'; break; case 'cloud-edit': icon_name = 'lib-cloud-preview-edit.png'; break; } // must be the last if (repo.status == 'read-only') { icon_name = 'lib-readonly.png'; } return mediaUrl + 'img/lib/' + size + '/' + icon_name; }, getDirentIcon: function (dirent, isBig) { if (!dirent) return ''; let size = Utils.isHiDPI() ? 48 : 24; size = isBig ? 192 : size; if (dirent.type == 'file') { return Utils.getFileIconUrl(dirent.name); } else { let readonly = false; if (dirent.permission && (dirent.permission === 'r' || dirent.permission === 'preview')) { readonly = true; } return Utils.getFolderIconUrl(readonly, size, dirent.has_been_shared_out); } }, getAdminTemplateDirentIcon: function (dirent) { if (dirent.is_file) { return this.getFileIconUrl(dirent.obj_name); } else { return this.getFolderIconUrl(); } }, getFolderIconUrl: function (readonly = false, size, sharedOut) { if (!size) { size = Utils.isHiDPI() ? 48 : 24; } size = size > 24 ? 192 : 24; return `${mediaUrl}img/folder${readonly ? '-read-only' : ''}${sharedOut ? '-shared-out' : ''}-${size}.png`; }, getFileIconUrl: function (filename) { let file_ext = ''; if (filename.lastIndexOf('.') == -1) { return mediaUrl + 'img/file/256/' + Utils.FILEEXT_ICON_MAP['default']; } else { file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); } if (Utils.FILEEXT_ICON_MAP[file_ext]) { return mediaUrl + 'img/file/256/' + Utils.FILEEXT_ICON_MAP[file_ext]; } else { return mediaUrl + 'img/file/256/' + Utils.FILEEXT_ICON_MAP['default']; } }, getLibIconTitle: function (repo) { var title; let permission = repo.permission || repo.share_permission; // Compatible with regular repo and repo shared if (repo.encrypted) { title = gettext('Encrypted library'); } else if (repo.is_admin) { // shared with 'admin' permission title = gettext('Admin access'); } else { switch (permission) { case 'rw': title = gettext('Read-Write library'); break; case 'r': title = gettext('Read-Only library'); break; case 'cloud-edit': title = gettext('Online Read-Write library'); break; case 'preview': title = gettext('Online Read-Only library'); break; } } return title; }, getFolderIconTitle: function (options) { var title; switch (options.permission) { case 'rw': title = gettext('Read-Write folder'); break; case 'r': title = gettext('Read-Only folder'); break; case 'cloud-edit': title = gettext('Online Read-Write folder'); break; case 'preview': title = gettext('Online Read-Only folder'); break; } return title; }, getFolderOperationList: function (isRepoOwner, currentRepoInfo, dirent, isContextmenu) { let list = []; const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, PERMISSION, OPEN_VIA_CLIENT } = TextTranslation; const permission = dirent.permission; const { isCustomPermission, customPermission } = Utils.getUserPermission(permission); if (isContextmenu) { if (permission == 'rw' || permission == 'r') { list.push(DOWNLOAD); } if (isCustomPermission && customPermission.permission.download) { list.push(DOWNLOAD); } if (Utils.isHasPermissionToShare(currentRepoInfo, permission, dirent)) { list.push(SHARE); } if (permission == 'rw' || permission == 'cloud-edit') { list.push(DELETE, 'Divider'); } if (isCustomPermission && customPermission.permission.delete) { list.push(DELETE, 'Divider'); } } if (permission == 'rw' || permission == 'cloud-edit') { list.push(RENAME, MOVE); } if (isCustomPermission && customPermission.permission.modify) { list.push(RENAME, MOVE); } if (permission == 'rw' || permission == 'cloud-edit') { list.push(COPY); } if (isCustomPermission && customPermission.permission.copy) { list.push(COPY); } if (permission == 'rw') { if (folderPermEnabled && ((isRepoOwner && currentRepoInfo.has_been_shared_out) || currentRepoInfo.is_admin)) { list.push('Divider', PERMISSION); } list.push('Divider', OPEN_VIA_CLIENT); } if (permission == 'r' && !currentRepoInfo.encrypted) { list.push(COPY); } // if the last item of menuList is ‘Divider’, delete the last item if (list[list.length - 1] === 'Divider') { list.pop(); } return list; }, getFileOperationList: function (isRepoOwner, currentRepoInfo, dirent, isContextmenu) { let list = []; const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK, UNFREEZE_DOCUMENT, FREEZE_DOCUMENT, HISTORY, ACCESS_LOG, PROPERTIES, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT, CONVERT_AND_EXPORT, CONVERT_TO_MARKDOWN, CONVERT_TO_DOCX, EXPORT_DOCX, CONVERT_TO_SDOC, EXPORT_SDOC } = TextTranslation; const permission = dirent.permission; const { isCustomPermission, customPermission } = Utils.getUserPermission(permission); if (isContextmenu) { if (permission == 'rw' || permission == 'r') { list.push(DOWNLOAD); } if (isCustomPermission && customPermission.permission.download) { list.push(DOWNLOAD); } if (Utils.isHasPermissionToShare(currentRepoInfo, permission, dirent)) { list.push(SHARE); } if (permission == 'rw' || permission == 'cloud-edit') { if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) { list.push(DELETE); } list.push('Divider'); } if (isCustomPermission && customPermission.permission.delete) { if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) { list.push(DELETE); } list.push('Divider'); } } if (permission == 'rw' || permission == 'cloud-edit') { if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) { list.push(RENAME, MOVE); } } if (isCustomPermission && customPermission.permission.modify) { if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) { list.push(RENAME, MOVE); } } if (permission == 'rw' || permission == 'cloud-edit') { list.push(COPY); } if (isCustomPermission) { if (customPermission.permission.copy) { list.push(COPY); } } if (permission == 'rw') { if (enableFileTags) { list.push(TAGS); } if (isPro) { if (dirent.is_locked) { if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice' || isRepoOwner || currentRepoInfo.is_admin) { if (!dirent.name.endsWith('.sdoc')) { list.push(UNLOCK); } } } else { if (!dirent.name.endsWith('.sdoc')) { list.push(LOCK); } } } list.push('Divider'); if (isPro && !dirent.is_locked && dirent.name.endsWith('.sdoc')) { list.push(FREEZE_DOCUMENT); } if (isPro && dirent.is_locked && dirent.name.endsWith('.sdoc')) { list.push(UNFREEZE_DOCUMENT); } } if ((permission == 'rw' || permission == 'cloud-edit') && enableSeadoc && !currentRepoInfo.encrypted) { if (dirent.name.endsWith('.md') || dirent.name.endsWith('.docx')) { list.push(CONVERT_TO_SDOC); } if (dirent.name.endsWith('.sdoc')) { if (Utils.isDesktop()) { let subOpList = [CONVERT_TO_MARKDOWN, CONVERT_TO_DOCX, EXPORT_DOCX, EXPORT_SDOC]; list.push({ ...CONVERT_AND_EXPORT, subOpList }); } else { list.push(CONVERT_TO_MARKDOWN); list.push(CONVERT_TO_DOCX); list.push(EXPORT_DOCX); list.push(EXPORT_SDOC); } } } if (permission == 'rw') { list.push('Divider'); list.push(PROPERTIES, HISTORY); if (isPro && fileAuditEnabled) { list.push(ACCESS_LOG); } list.push('Divider', OPEN_VIA_CLIENT); } if (permission == 'r') { if (!currentRepoInfo.encrypted) { list.push(COPY); } list.push(HISTORY); } if (permission == 'rw' && enableOnlyoffice && onlyofficeConverterExtensions.includes(Utils.getFileExtension(dirent.name, false))) { list.push(ONLYOFFICE_CONVERT); } // if the last item of menuList is ‘Divider’, delete the last item if (list[list.length - 1] === 'Divider') { list.pop(); } // Remove adjacent excess 'Divider' for (let i = 0; i < list.length; i++) { if (list[i] === 'Divider' && list[i + 1] === 'Divider') { list.splice(i, 1); i--; } } return list; }, getFileExtension: function (fileName, withoutDot) { let parts = fileName.toLowerCase().split('.'); return withoutDot ? parts.pop() : '.' + parts.pop(); }, getDirentOperationList: function (isRepoOwner, currentRepoInfo, dirent, isContextmenu) { const operationListGetter = dirent.type === 'dir' ? Utils.getFolderOperationList : Utils.getFileOperationList; return operationListGetter(isRepoOwner, currentRepoInfo, dirent, isContextmenu); }, sharePerms: function (permission) { var title; switch (permission) { case 'rw': title = gettext('Read-Write'); break; case 'r': title = gettext('Read-Only'); break; case 'admin': title = gettext('Admin'); break; case 'cloud-edit': title = gettext('Online Read-Write'); break; case 'preview': title = gettext('Online Read-Only'); break; case 'invisible': title = gettext('Invisible'); break; } return title; }, sharePermsExplanation: function (permission) { var title; switch (permission) { case 'rw': title = gettext('User can read, write, upload, download and sync files.'); break; case 'r': title = gettext('User can read, download and sync files.'); break; case 'admin': title = gettext('Besides Write permission, user can also share the library.'); break; case 'cloud-edit': title = gettext('User can view and edit file online via browser. Files can\'t be downloaded.'); break; case 'preview': title = gettext('User can only view files online via browser. Files can\'t be downloaded.'); break; case 'invisible': title = gettext('User can not see this folder.'); break; } return title; }, getShareLinkPermissionObject: function (permission) { switch (permission) { case 'preview_download': return { value: permission, text: gettext('Preview and download'), permissionDetails: { 'can_edit': false, 'can_download': true, 'can_upload': false } }; case 'preview_only': return { value: permission, text: gettext('Preview only'), permissionDetails: { 'can_edit': false, 'can_download': false, 'can_upload': false } }; case 'download_upload': return { value: permission, text: gettext('Download and upload'), permissionDetails: { 'can_edit': false, 'can_download': true, 'can_upload': true } }; case 'edit_download': return { value: permission, text: gettext('Edit on cloud and download'), permissionDetails: { 'can_edit': true, 'can_download': true, 'can_upload': false } }; case 'cloud_edit': return { value: permission, text: gettext('Edit on cloud only'), permissionDetails: { 'can_edit': true, 'can_download': false, 'can_upload': false } }; } return { text: '', }; }, formatSize: function (options) { /* * param: {bytes, precision} */ var bytes = options.bytes; var precision = options.precision || 0; var kilobyte = 1000; var megabyte = kilobyte * 1000; var gigabyte = megabyte * 1000; var terabyte = gigabyte * 1000; if ((bytes >= 0) && (bytes < kilobyte)) { return bytes + ' B'; } else if ((bytes >= kilobyte) && (bytes < megabyte)) { return (bytes / kilobyte).toFixed(precision) + ' KB'; } else if ((bytes >= megabyte) && (bytes < gigabyte)) { return (bytes / megabyte).toFixed(precision) + ' MB'; } else if ((bytes >= gigabyte) && (bytes < terabyte)) { return (bytes / gigabyte).toFixed(precision) + ' GB'; } else if (bytes >= terabyte) { return (bytes / terabyte).toFixed(precision) + ' TB'; } else { return bytes + ' B'; } }, formatBitRate: function (bits) { var Bs; if (typeof bits !== 'number') { return ''; } Bs = bits / 8; if (Bs >= 1000000000) { return (Bs / 1000000000).toFixed(2) + ' GB/s'; } if (Bs >= 1000000) { return (Bs / 1000000).toFixed(2) + ' MB/s'; } if (Bs >= 1000) { return (Bs / 1000).toFixed(2) + ' kB/s'; } return Bs.toFixed(2) + ' B/s'; }, isMarkdownFile: function (filePath) { let index = filePath.lastIndexOf('.'); if (index === -1) { return false; } else { let type = filePath.substring(index).toLowerCase(); if (type === '.md' || type === '.markdown') { return true; } else { return false; } } }, isSdocFile: function (filePath) { let index = filePath.lastIndexOf('.'); if (index === -1) { return false; } else { let type = filePath.substring(index).toLowerCase(); if (type === '.sdoc') { return true; } else { return false; } } }, isDocxFile: function (filePath) { let index = filePath.lastIndexOf('.'); if (index === -1) { return false; } else { let type = filePath.substring(index).toLowerCase(); if (type === '.docx') { return true; } else { return false; } } }, isDescriptionSupportedFile: function (filePath) { return Utils.isSdocFile(filePath) || Utils.isMarkdownFile(filePath) || Utils.pdfCheck(filePath) || Utils.isDocxFile(filePath); }, isFileMetadata: function (type) { return type === PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES; }, isInternalFileLink: function (url, repoID) { var re = new RegExp(serviceURL + '/lib/' + repoID + '/file.*'); return re.test(url); }, isInternalMarkdownLink: function (url, repoID) { // eslint-disable-next-line var re = new RegExp(serviceURL + '/lib/' + repoID + '.*\.md$'); return re.test(url); }, isInternalDirLink: function (url, repoID) { var re = new RegExp(serviceURL + '/library/' + repoID + '.*'); return re.test(url); }, getPathFromInternalMarkdownLink: function (url, repoID) { // eslint-disable-next-line var re = new RegExp(serviceURL + '/lib/' + repoID + '/file' + '(.*\.md)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); return path; }, getPathFromInternalDirLink: function (url, repoID) { var re = new RegExp(serviceURL + '/library/' + repoID + '(/.*)'); var array = re.exec(url); var path = decodeURIComponent(array[1]); path = path.slice(1); path = path.slice(path.indexOf('/')); return path; }, isWikiInternalMarkdownLink: function (url, slug) { slug = encodeURIComponent(slug); // eslint-disable-next-line var re = new RegExp(serviceURL + '/published/' + slug + '.*\.md$'); return re.test(url); }, isWikiInternalDirLink: function (url, slug) { slug = encodeURIComponent(slug); var re = new RegExp(serviceURL + '/published/' + slug + '.*'); return re.test(url); }, getPathFromWikiInternalMarkdownLink: function (url, slug) { slug = encodeURIComponent(slug); // eslint-disable-next-line var re = new RegExp(serviceURL + '/published/' + slug + '(.*\.md)'); var array = re.exec(url); var path = array[1]; try { path = decodeURIComponent(path); } catch (err) { path = path.replace(/%/g, '%25'); path = decodeURIComponent(path); } return path; }, getPathFromWikiInternalDirLink: function (url, slug) { slug = encodeURIComponent(slug); var re = new RegExp(serviceURL + '/published/' + slug + '(/.*)'); var array = re.exec(url); var path = array[1]; try { path = decodeURIComponent(path); } catch (err) { path = path.replace(/%/g, '%25'); path = decodeURIComponent(path); } return path; }, compareTwoWord: function (wordA, wordB) { // compare wordA and wordB at lower case // if wordA >= wordB, return 1 // if wordA < wordB, return -1 return compareTwoString(wordA, wordB); }, // compare two strings which may have digits in them // and compare those digits as number instead of string compareStrWithNumbersIn: function (a, b) { var reParts = /\d+|\D+/g; var reDigit = /\d/; var aParts = a.match(reParts); var bParts = b.match(reParts); var isDigitPart; var len = Math.min(aParts.length, bParts.length); var aPart; var bPart; if (aParts && bParts && (isDigitPart = reDigit.test(aParts[0])) == reDigit.test(bParts[0])) { // Loop through each substring part to compare the overall strings. for (var i = 0; i < len; i++) { aPart = aParts[i]; bPart = bParts[i]; if (isDigitPart) { aPart = parseInt(aPart, 10); bPart = parseInt(bPart, 10); } if (aPart != bPart) { return aPart < bPart ? -1 : 1; } // Toggle the value of isDigitPart since the parts will alternate. isDigitPart = !isDigitPart; } } // Use normal comparison. return (a >= b) - (a <= b); }, sortRepos: function (repos, sortBy, sortOrder) { const _this = this; let comparator; switch (`${sortBy}-${sortOrder}`) { case 'name-asc': comparator = function (a, b) { if (!a.repo_name) { return 1; } if (!b.repo_name) { return -1; } var result = _this.compareTwoWord(a.repo_name, b.repo_name); return result; }; break; case 'name-desc': comparator = function (a, b) { if (!a.repo_name) { return -1; } if (!b.repo_name) { return 1; } var result = _this.compareTwoWord(a.repo_name, b.repo_name); return -result; }; break; case 'time-asc': comparator = function (a, b) { return a.last_modified < b.last_modified ? -1 : 1; }; break; case 'time-desc': comparator = function (a, b) { return a.last_modified < b.last_modified ? 1 : -1; }; break; case 'size-asc': comparator = function (a, b) { if (a.size === b.size) { let result = _this.compareTwoWord(a.repo_name, b.repo_name); return result; } return a.size_original < b.size_original ? -1 : 1; }; break; case 'size-desc': comparator = function (a, b) { if (a.size === b.size) { let result = _this.compareTwoWord(a.repo_name, b.repo_name); return -result; } return a.size_original < b.size_original ? 1 : -1; }; break; } repos.sort(comparator); return repos; }, sortDirents: function (items, sortBy, sortOrder) { const _this = this; let comparator; switch (`${sortBy}-${sortOrder}`) { case 'name-asc': comparator = function (a, b) { var result = _this.compareTwoWord(a.name, b.name); return result; }; break; case 'name-desc': comparator = function (a, b) { var result = _this.compareTwoWord(a.name, b.name); return -result; }; break; case 'time-asc': comparator = function (a, b) { return a.mtime < b.mtime ? -1 : 1; }; break; case 'time-desc': comparator = function (a, b) { return a.mtime < b.mtime ? 1 : -1; }; break; case 'size-asc': comparator = function (a, b) { if (a.type == 'dir' && b.type == 'dir') { return 0; } return a.size_original < b.size_original ? -1 : 1; }; break; case 'size-desc': comparator = function (a, b) { if (a.type == 'dir' && b.type == 'dir') { return 0; } return a.size_original < b.size_original ? 1 : -1; }; break; } items.sort((a, b) => { if (a.type == 'dir' && b.type == 'file') { return -1; } else if (a.type == 'file' && b.type == 'dir') { return 1; } else { return comparator(a, b); } }); return items; }, // sort dirents in shared folder sortDirentsInSharedDir: function (items, sortBy, sortOrder) { const _this = this; let comparator; switch (`${sortBy}-${sortOrder}`) { case 'name-asc': comparator = function (a, b) { let result; if (a.is_dir) { result = _this.compareTwoWord(a.folder_name, b.folder_name); } else { result = _this.compareTwoWord(a.file_name, b.file_name); } return result; }; break; case 'name-desc': comparator = function (a, b) { let result; if (a.is_dir) { result = _this.compareTwoWord(a.folder_name, b.folder_name); } else { result = _this.compareTwoWord(a.file_name, b.file_name); } return -result; }; break; case 'time-asc': comparator = function (a, b) { return a.last_modified < b.last_modified ? -1 : 1; }; break; case 'time-desc': comparator = function (a, b) { return a.last_modified < b.last_modified ? 1 : -1; }; break; case 'size-asc': comparator = function (a, b) { if (a.is_dir) { return 0; } else { return a.size < b.size ? -1 : 1; } }; break; case 'size-desc': comparator = function (a, b) { if (a.is_dir) { return 0; } else { return a.size < b.size ? 1 : -1; } }; break; } items.sort((a, b) => { if (a.is_dir && !b.is_dir) { return -1; } else if (!a.is_dir && b.is_dir) { return 1; } else { return comparator(a, b); } }); return items; }, /* * only used in the 'catch' part of a seafileAPI request */ getErrorMsg: function (error, showPermissionDeniedTip) { let errorMsg = ''; if (error.response) { if (error.response.status == 403) { if (showPermissionDeniedTip) { toaster.danger( , { id: 'permission_denied', duration: 3600 } ); } errorMsg = gettext('Permission denied'); } else if (error.response.status == 429) { errorMsg = gettext('Too many requests'); } else if (error.response.data && error.response.data['error_msg']) { errorMsg = error.response.data['error_msg']; } else { errorMsg = gettext('Error'); } } else { errorMsg = gettext('Please check the network.'); } return errorMsg; }, changeMarkdownNodes: function (nodes, fn) { nodes.forEach((item) => { fn(item); if (item.children && item.children.length > 0) { Utils.changeMarkdownNodes(item.children, fn); } }); return nodes; }, chooseLanguage: function (suffix) { let mode; switch (suffix) { case 'py': mode = 'python'; break; case 'js': mode = 'javascript'; break; case 'c': mode = 'c'; break; case 'cpp': mode = 'cpp'; break; case 'cs': mode = 'csharp'; break; case 'java': mode = 'java'; break; case 'mdf': mode = 'text/x-sql'; break; case 'html': mode = 'html'; break; case 'sh': mode = 'shell'; break; default: mode = suffix; } return mode; }, DARK_COLOR_MAP: { // old color 'red': '#D11507', 'orange': '#FF8C00', 'yellow': '#EDEF00', 'green': '#006400', 'cyan': '#00E0E1', 'blue': '#2510A3', 'indigo': '#350C56', 'purple': '#551054', 'pink': '#E3A5B0', 'azure': '#C4D0D0', 'lime': '#00E100', 'teal': '#006A6B', 'gray': '#545454', // new color '#FFA8A8': '#E49090', '#FFA94D': '#E39136', '#FFD43B': '#E0B815', '#A0EC50': '#83CF32', '#A9E34B': '#8DC72E', '#63E6BE': '#43CAA4', '#4FD2C9': '#2DB9B0', '#72C3FC': '#57ABE3', '#91A7FF': '#7A91E7', '#E599F7': '#CC82DE', '#B197FC': '#9B82E5', '#F783AC': '#DF6D97', '#CED4DA': '#A8ADB2', }, getDarkColor: function (color) { let darkColor; darkColor = this.DARK_COLOR_MAP[color]; return darkColor; }, getCopySuccessfulMessage: function (dirNames) { let message; let dirNamesLength = dirNames.length; if (dirNamesLength === 1) { message = gettext('Successfully copied %(name)s.'); } else if (dirNamesLength === 2) { message = gettext('Successfully copied %(name)s and 1 other item.'); } else { message = gettext('Successfully copied %(name)s and %(amount)s other items.'); message = message.replace('%(amount)s', dirNamesLength - 1); } message = message.replace('%(name)s', dirNames[0]); return message; }, getMoveSuccessMessage: function (dirNames) { let message; let dirNamesLength = dirNames.length; if (dirNamesLength === 1) { message = gettext('Successfully moved %(name)s.'); } else if (dirNamesLength === 2) { message = gettext('Successfully moved %(name)s and 1 other item.'); } else { message = gettext('Successfully moved %(name)s and %(amount)s other items.'); message = message.replace('%(amount)s', dirNamesLength - 1); } message = message.replace('%(name)s', dirNames[0]); return message; }, getCopyFailedMessage: function (dirNames) { let message; let dirNamesLength = dirNames.length; if (dirNamesLength > 1) { message = gettext('Failed to copy %(name)s and %(amount)s other item(s).'); message = message.replace('%(amount)s', dirNamesLength - 1); } else { message = gettext('Failed to copy %(name)s.'); } message = message.replace('%(name)s', dirNames[0]); return message; }, getMoveFailedMessage: function (dirNames) { let message; let dirNamesLength = dirNames.length; if (dirNamesLength > 1) { message = gettext('Failed to move %(name)s and %(amount)s other item(s).'); message = message.replace('%(amount)s', dirNamesLength - 1); } else { message = gettext('Failed to move %(name)s.'); } message = message.replace('%(name)s', dirNames[0]); return message; }, handleSearchedItemClick: function (searchedItem) { if (searchedItem.is_dir === true) { let url = siteRoot + 'library/' + searchedItem.repo_id + '/' + searchedItem.repo_name + searchedItem.path; let newWindow = window.open('about:blank'); newWindow.location.href = url; } else { let url = siteRoot + 'lib/' + searchedItem.repo_id + '/file' + Utils.encodePath(searchedItem.path); let newWindow = window.open('about:blank'); newWindow.location.href = url; } }, generatePassword: function (length, hasNum = 1, hasChar = 1, hasSymbol = 1) { var password = ''; // 65~90:A~Z password += String.fromCharCode(Math.floor((Math.random() * (90 - 65)) + 65)); // 97~122:a~z password += String.fromCharCode(Math.floor((Math.random() * (122 - 97)) + 97)); // 48~57:0~9 password += String.fromCharCode(Math.floor((Math.random() * (57 - 48)) + 48)); // 33~47:!~/ password += String.fromCharCode(Math.floor((Math.random() * (47 - 33)) + 33)); // 33~47:!~/ // 48~57:0~9 // 58~64::~@ // 65~90:A~Z // 91~96:[~` // 97~122:a~z // 123~127:{~ for (var i = 0; i < length - 4; i++) { var num = Math.floor((Math.random() * (127 - 33)) + 33); password += String.fromCharCode(num); } return password; }, pathNormalize: function (originalPath) { let oldPath = originalPath.split('/'); let newPath = []; for (let i = 0; i < oldPath.length; i++) { if (oldPath[i] === '.' || oldPath[i] === '') { continue; } else if (oldPath[i] === '..') { newPath.pop(); } else { newPath.push(oldPath[i]); } } return newPath.join('/'); }, getEventData: function (event, data) { if (event.target.dataset) { return event.target.dataset[data]; } return event.target.getAttribute('data-' + data); }, /** * Check whether user has permission to share a dirent. * If dirent is none, then check whether the user can share the repo * scene 1: root path or folder path, control the share button in the toolbar * scene 2: selected a dirent, control the share button in the toolbar dropdown-menu * scene 3: dirent list(grid list), control the share button in the dirent-item or righe-menu * * @param {*} repoInfo * @param {*} userDirPermission * @param {*} dirent */ isHasPermissionToShare: function (repoInfo, userDirPermission, dirent) { const { isCustomPermission, customPermission } = Utils.getUserPermission(userDirPermission); if (isCustomPermission) { const { download_external_link } = customPermission.permission; return download_external_link; } let { is_admin: isAdmin, is_virtual: isVirtual, encrypted: repoEncrypted, owner_email: ownerEmail } = repoInfo; let isRepoOwner = ownerEmail === username; if (repoEncrypted) { return true; } // for 'file' & 'dir' if (dirent) { if (userDirPermission == 'rw' || userDirPermission == 'r') { // can generate internal link return true; } } // the root path or the dirent type is dir let hasGenerateShareLinkPermission = false; if (canGenerateShareLink && (userDirPermission == 'rw' || userDirPermission == 'r')) { hasGenerateShareLinkPermission = true; return hasGenerateShareLinkPermission; } let hasGenerateUploadLinkPermission = false; if (canGenerateUploadLink && (userDirPermission == 'rw')) { hasGenerateUploadLinkPermission = true; return hasGenerateUploadLinkPermission; } let hasDirPrivateSharePermission = false; if (!isVirtual && (isRepoOwner || isAdmin)) { hasDirPrivateSharePermission = true; return hasDirPrivateSharePermission; } return false; }, registerGlobalVariable: function (namespace, key, value) { if (!window[namespace]) { window[namespace] = {}; } window[namespace][key] = value; }, formatTime: function (seconds) { var ss = parseInt(seconds); var mm = 0; var hh = 0; if (ss > 60) { mm = parseInt(ss / 60); ss = parseInt(ss % 60); } if (mm > 60) { hh = parseInt(mm / 60); mm = parseInt(mm % 60); } var result = ('00' + parseInt(ss)).slice(-2); if (mm > 0) { result = ('00' + parseInt(mm)).slice(-2) + ':' + result; } else { result = '00:' + result; } if (hh > 0) { result = ('00' + parseInt(hh)).slice(-2) + ':' + result; } else { result = '00:' + result; } return result; }, hasNextPage(curPage, perPage, totalCount) { return curPage * perPage < totalCount; }, getStrengthLevel: function (pwd) { const _this = this; var num = 0; if (pwd.length < shareLinkPasswordMinLength) { return 0; } else { for (var i = 0; i < pwd.length; i++) { // return the unicode // bitwise OR num |= _this.getCharMode(pwd.charCodeAt(i)); } return _this.calculateBitwise(num); } }, getCharMode: function (n) { if (n >= 48 && n <= 57) // nums return 1; if (n >= 65 && n <= 90) // uppers return 2; if (n >= 97 && n <= 122) // lowers return 4; else return 8; }, calculateBitwise: function (num) { var level = 0; for (var i = 0; i < 4; i++) { // bitwise AND if (num & 1) level++; // Right logical shift num >>>= 1; } return level; }, getSharedPermission: function (item) { let permission = item.permission; if (item.is_admin) { permission = 'admin'; } if (item.permission.startsWith('custom-')) { permission = item.permission.slice(7); } return permission; }, getUserPermission: function (userPerm) { const { custom_permission } = window; const common_permissions = ['rw', 'r', 'admin', 'cloud-edit', 'preview']; // visit the shared repo(virtual repo) by custom permission if (!custom_permission || common_permissions.indexOf(userPerm) > -1) { return { isCustomPermission: false }; } // userPerm is startsWith 'custom-' if (custom_permission) { const permissionId = custom_permission.id; const userPermId = parseInt(userPerm.split('-')[1]); if (permissionId === userPermId) { return { isCustomPermission: true, customPermission: custom_permission }; } // TODO user set custom permission on folder } return { isCustomPermission: false }; }, // for a11y onKeyDown: function (e) { if (e.key == 'Enter' || e.key == 'Space') { e.target.click(); } }, updateTabTitle: function (content) { const title = document.getElementsByTagName('title')[0]; title.innerText = content; }, generateHistoryURL: function (siteRoot, repoID, path) { if (!siteRoot || !repoID || !path) return ''; return siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + this.encodePath(path); }, generateRevisionURL: function (siteRoot, repoID, path) { if (!siteRoot || !repoID || !path) return ''; return siteRoot + 'repo/sdoc_revision/' + repoID + '/?p=' + this.encodePath(path); }, generateRevisionsURL: function (siteRoot, repoID, path) { if (!siteRoot || !repoID || !path) return ''; return siteRoot + 'repo/sdoc_revisions/' + repoID + '/?p=' + this.encodePath(path); }, isFunction: function (functionToCheck) { const getType = {}; return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; }, getUrlSearches() { const search = location.search; let searchParams = {}; if (search.length === 0) { return searchParams; } let allSearches = search.split('?')[1]; let allSearchesArr = allSearches.split('&'); allSearchesArr.forEach(item => { let itemArr = item.split('='); searchParams[itemArr[0]] = decodeURI(itemArr[1]); }); return searchParams; }, // If value is null, delete the search parameter; else, add or update the search parameter. updateSearchParameter(key, value) { const { origin, pathname } = location; const searchParams = this.getUrlSearches(); searchParams[key] = value; let newSearch = '?'; for (let key in searchParams) { let value = searchParams[key]; if (value) { newSearch = newSearch === '?' ? `?${key}=${value}` : `${newSearch}&${key}=${value}`; } } history.replaceState(null, '', origin + pathname + newSearch); }, isRelativePath(url) { let RgExp = new RegExp('^(?:[a-z]+:)?//', 'i'); return !RgExp.test(url); }, isMac() { const platform = navigator.platform; return (platform == 'Mac68K') || (platform == 'MacPPC') || (platform == 'Macintosh') || (platform == 'MacIntel'); }, }; export const isMobile = (typeof (window) !== 'undefined') && (window.innerWidth < 768 || navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null); export const evaluatePasswordStrength = (password) => { let strength = 0; const length = password.length; const hasUppercase = /[A-Z]/.test(password); const hasLowercase = /[a-z]/.test(password); const hasNumbers = /\d/.test(password); const hasSpecialChars = /[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\]/.test(password); // Increased strength based on length if (length === 0) return 'empty'; if (length >= 16) strength += 4; else if (length >= 12) strength += 3; else if (length >= 8) strength += 2; else if (length >= 6) strength += 1; // Increased strength based on character type if (hasUppercase) strength += 1; if (hasLowercase) strength += 1; if (hasNumbers) strength += 1; if (hasSpecialChars) strength += 1; // Determine password strength if (strength >= 8) return 'very_strong'; if (strength >= 6) return 'strong'; if (strength >= 4) return 'medium'; return 'weak'; }; export const validatePassword = (password) => { const { userStrongPasswordRequired } = window.app.pageOptions; const passwordStrength = evaluatePasswordStrength(password); const requiredStrengths = userStrongPasswordRequired ? ['strong', 'very_strong'] : ['medium', 'strong', 'very_strong']; return requiredStrengths.includes(passwordStrength); }; export const validateName = (newName) => { let isValid = true; let errMessage = ''; if (!newName || !newName.trim()) { isValid = false; errMessage = gettext('Name is required'); return { isValid, errMessage }; } if (newName.includes('/')) { isValid = false; errMessage = gettext('Name cannot contain slash'); return { isValid, errMessage }; } if (newName.includes('`')) { isValid = false; errMessage = gettext('Name cannot contain backtick'); return { isValid, errMessage }; } if (newName.includes('\\')) { isValid = false; errMessage = gettext('Name cannot contain backslash'); return { isValid, errMessage }; } return { isValid, errMessage }; };