1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-05 17:14:28 +00:00
seahub/frontend/src/utils/utils.js

1762 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = `<span class="op-target ellipsis ellipsis-op-target" title=${targetStr}>${targetStr}</span>`;
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(
<PermissionDeniedTip />,
{ 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~90A~Z
password += String.fromCharCode(Math.floor((Math.random() * (90 - 65)) + 65));
// 97~122a~z
password += String.fromCharCode(Math.floor((Math.random() * (122 - 97)) + 97));
// 48~570~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~570~9
// 58~64:~@
// 65~90A~Z
// 91~96[~`
// 97~122a~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 };
};