解决v2.0.0公测版大家反馈的问题

This commit is contained in:
Aicirou 2020-05-20 02:11:53 +08:00
parent 78ab2ed495
commit 6a63ea8e85
8 changed files with 932 additions and 820 deletions

View File

@ -1,13 +1,13 @@
var authConfig = {
"siteName": "Achirou's Cloud", // 网站名称
"version" : "1.1.0", // 程序版本
"theme" : "acrou",
siteName: "Achirou's Cloud", // 网站名称
version: "1.1.0", // 程序版本
theme: "acrou",
/*"client_id": "202264815644.apps.googleusercontent.com",
"client_secret": "X4Z3ca8xfWDb1Voo-F9a7ZxJ",*/
// 强烈推荐使用自己的 client_id 和 client_secret
"client_id": "",
"client_secret": "",
"refresh_token": "", // 授权 token
client_id: "",
client_secret: "",
refresh_token: "", // 授权 token
/**
* 设置要显示的多个云端硬盘按格式添加多个
* [id]: 可以是 团队盘id子文件夹id或者"root"代表个人盘根目录
@ -21,7 +21,7 @@ var authConfig = {
* 不需要 Basic Auth 的盘保持 user pass 同时为空即可直接不设置也可以
* 注意对于id设置为为子文件夹id的盘将不支持搜索功能不影响其他盘
*/
"roots": [
roots: [
{
id: "",
name: "TeamDrive",
@ -30,45 +30,52 @@ var authConfig = {
{
id: "root",
name: "PrivateDrive",
user: '',
user: "",
pass: "",
protect_file_link: true
protect_file_link: true,
},
{
id: "",
name: "folder1",
pass: "",
}
},
],
"default_gd": 0,
default_gd: 0,
/**
* 文件列表页面每页显示的数量推荐设置值为 100 1000 之间
* 如果设置大于1000会导致请求 drive api 时出错
* 如果设置的值过小会导致文件列表页面滚动条增量加载分页加载失效
* 此值的另一个作用是如果目录内文件数大于此设置值即需要多页展示的将会对首次列目录结果进行缓存
*/
"files_list_page_size": 50,
files_list_page_size: 50,
/**
* 搜索结果页面每页显示的数量推荐设置值为 50 1000 之间
* 如果设置大于1000会导致请求 drive api 时出错
* 如果设置的值过小会导致搜索结果页面滚动条增量加载分页加载失效
* 此值的大小影响搜索操作的响应速度
*/
"search_result_list_page_size": 50,
search_result_list_page_size: 50,
// 确认有 cors 用途的可以开启
"enable_cors_file_down": false,
enable_cors_file_down: false,
/**
* 上面的 basic auth 已经包含了盘内全局保护的功能所以默认不再去认证 .password 文件内的密码;
* 如果在全局认证的基础上仍需要给某些目录单独进行 .password 文件内的密码验证的话将此选项设置为 true;
* 注意如果开启了 .password 文件密码验证每次列目录都会额外增加查询目录内 .password 文件是否存在的开销
*/
"enable_password_file_verify": true
enable_password_file_verify: true,
};
var themeOptions = {
//可选默认系统语言:en/zh-chs/zh-cht
languages: 'en'
}
languages: "en",
render: {
head_md: false,
readme_md: false,
// 是否显示文件/文件夹描述(默认不显示)
// Show file/folder description or not (not shown by default)
desc: false
},
};
/**
* global functions
@ -81,11 +88,11 @@ const FUNCS = {
let nothing = "";
let space = " ";
if (!keyword) return nothing;
return keyword.replace(/(!=)|['"=<>/\\:]/g, nothing)
return keyword
.replace(/(!=)|['"=<>/\\:]/g, nothing)
.replace(/[,|(){}]/g, space)
.trim()
}
.trim();
},
};
/**
@ -93,16 +100,16 @@ const FUNCS = {
* @type {{folder_mime_type: string, default_file_fields: string, gd_root_type: {share_drive: number, user_drive: number, sub_folder: number}}}
*/
const CONSTS = new (class {
default_file_fields = 'parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size';
default_file_fields =
"parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size";
gd_root_type = {
user_drive: 0,
share_drive: 1,
sub_folder: 2
sub_folder: 2,
};
folder_mime_type = 'application/vnd.google-apps.folder';
folder_mime_type = "application/vnd.google-apps.folder";
})();
// gd instances
var gds = [];
@ -118,8 +125,14 @@ function html(current_drive_order = 0, model = {}) {
@import url(https://cloud.jsonpop.cn/go2index/v2.0.0_beta/dist/style.css);
</style>
<script>
window.gdconfig = JSON.parse('${JSON.stringify({version: authConfig.version,themeOptions: themeOptions})}');
window.gds = JSON.parse('${JSON.stringify(authConfig.roots.map(it => it.name))}');
window.gdconfig = JSON.parse('${JSON.stringify({
version: authConfig.version,
themeOptions: themeOptions,
})}');
window.themeOptions = JSON.parse('${JSON.stringify(themeOptions)}');
window.gds = JSON.parse('${JSON.stringify(
authConfig.roots.map((it) => it.name)
)}');
window.MODEL = JSON.parse('${JSON.stringify(model)}');
window.current_drive_order = ${current_drive_order};
</script>
@ -130,9 +143,9 @@ function html(current_drive_order = 0, model = {}) {
</body>
</html>
`;
};
}
addEventListener('fetch', event => {
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
@ -145,11 +158,11 @@ async function handleRequest(request) {
for (let i = 0; i < authConfig.roots.length; i++) {
const gd = new googleDrive(authConfig, i);
await gd.init();
gds.push(gd)
gds.push(gd);
}
// 这个操作并行,提高效率
let tasks = [];
gds.forEach(gd => {
gds.forEach((gd) => {
tasks.push(gd.initRootType());
});
for (let task of tasks) {
@ -168,13 +181,16 @@ async function handleRequest(request) {
* @returns {Response}
*/
function redirectToIndexPage() {
return new Response('', {status: 301, headers: {'Location': `/${authConfig.default_gd}:/`}});
return new Response("", {
status: 301,
headers: { Location: `/${authConfig.default_gd}:/` },
});
}
if (path == '/') return redirectToIndexPage();
if (path.toLowerCase() == '/favicon.ico') {
if (path == "/") return redirectToIndexPage();
if (path.toLowerCase() == "/favicon.ico") {
// 后面可以找一个 favicon
return new Response('', {status: 404})
return new Response("", { status: 404 });
}
// 特殊命令格式
@ -187,46 +203,48 @@ async function handleRequest(request) {
if (order >= 0 && order < gds.length) {
gd = gds[order];
} else {
return redirectToIndexPage()
return redirectToIndexPage();
}
// basic auth
for (const r = gd.basicAuthResponse(request); r; ) return r;
command = match.groups.command;
// 搜索
if (command === 'search') {
if (request.method === 'POST') {
if (command === "search") {
if (request.method === "POST") {
// 搜索结果
return handleSearch(request, gd);
} else {
const params = url.searchParams;
// 搜索页面
return new Response(html(gd.order, {
q: params.get("q") || '',
return new Response(
html(gd.order, {
q: params.get("q") || "",
is_search_page: true,
root_type: gd.root_type
root_type: gd.root_type,
}),
{
status: 200,
headers: {'Content-Type': 'text/html; charset=utf-8'}
});
headers: { "Content-Type": "text/html; charset=utf-8" },
}
} else if (command === 'id2path' && request.method === 'POST') {
return handleId2Path(request, gd)
} else if (command === 'view'){
);
}
} else if (command === "id2path" && request.method === "POST") {
return handleId2Path(request, gd);
} else if (command === "view") {
const params = url.searchParams;
return gd.view(params.get("url"), request.headers.get('Range'));
} else if (command!=='down' && request.method === 'GET'){
return gd.view(params.get("url"), request.headers.get("Range"));
} else if (command !== "down" && request.method === "GET") {
return new Response(html(gd.order, { root_type: gd.root_type }), {
status: 200,
headers: {'Content-Type': 'text/html; charset=utf-8'}
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
}
const reg = new RegExp(`^(/\\d+:)${command}/`, 'g');
const reg = new RegExp(`^(/\\d+:)${command}/`, "g");
path = path.replace(reg, (p1, p2) => {
return p2+'/'
})
return p2 + "/";
});
// 期望的 path 格式
const common_reg = /^\/\d+:\/.*$/g;
try {
@ -238,56 +256,67 @@ async function handleRequest(request) {
if (order >= 0 && order < gds.length) {
gd = gds[order];
} else {
return redirectToIndexPage()
return redirectToIndexPage();
}
} catch (e) {
return redirectToIndexPage()
return redirectToIndexPage();
}
// basic auth
// for (const r = gd.basicAuthResponse(request); r;) return r;
const basic_auth_res = gd.basicAuthResponse(request);
path = path.replace(gd.url_path_prefix, '') || '/';
if (request.method == 'POST') {
path = path.replace(gd.url_path_prefix, "") || "/";
if (request.method == "POST") {
return basic_auth_res || apiRequest(request, gd);
}
let action = url.searchParams.get('a');
let action = url.searchParams.get("a");
if (path.substr(-1) == '/' || action != null) {
return basic_auth_res || new Response(html(gd.order, {root_type: gd.root_type}), {
if (path.substr(-1) == "/" || action != null) {
return (
basic_auth_res ||
new Response(html(gd.order, { root_type: gd.root_type }), {
status: 200,
headers: {'Content-Type': 'text/html; charset=utf-8'}
});
headers: { "Content-Type": "text/html; charset=utf-8" },
})
);
} else {
if (path.split('/').pop().toLowerCase() == ".password") {
if (
path
.split("/")
.pop()
.toLowerCase() == ".password"
) {
return basic_auth_res || new Response("", { status: 404 });
}
let file = await gd.file(path);
let range = request.headers.get('Range');
let range = request.headers.get("Range");
if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res;
const is_down = !(command && command=='down');
const is_down = !(command && command == "down");
return gd.down(file.id, range, is_down);
}
}
async function apiRequest(request, gd) {
let url = new URL(request.url);
let path = url.pathname;
path = path.replace(gd.url_path_prefix, '') || '/';
path = path.replace(gd.url_path_prefix, "") || "/";
let option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}}
let option = { status: 200, headers: { "Access-Control-Allow-Origin": "*" } };
if (path.substr(-1) == '/') {
if (path.substr(-1) == "/") {
let deferred_pass = gd.password(path);
let body = await request.text();
body = JSON.parse(body);
// 这样可以提升首次列目录时的速度。缺点是如果password验证失败也依然会产生列目录的开销
let deferred_list_result = gd.list(path, body.page_token, Number(body.page_index));
let deferred_list_result = gd.list(
path,
body.page_token,
Number(body.page_index)
);
// check .password file, if `enable_password_file_verify` is true
if (authConfig['enable_password_file_verify']) {
if (authConfig["enable_password_file_verify"]) {
let password = await gd.password(path);
// console.log("dir password", password);
if (password && password.replace("\n", "") !== body.password) {
@ -300,18 +329,24 @@ async function apiRequest(request, gd) {
return new Response(JSON.stringify(list_result), option);
} else {
let file = await gd.file(path);
let range = request.headers.get('Range');
let range = request.headers.get("Range");
return new Response(JSON.stringify(file));
}
}
// 处理 search
async function handleSearch(request, gd) {
const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}};
const option = {
status: 200,
headers: { "Access-Control-Allow-Origin": "*" },
};
let body = await request.text();
body = JSON.parse(body);
let search_result = await
gd.search(body.q || '', body.page_token, Number(body.page_index));
let search_result = await gd.search(
body.q || "",
body.page_token,
Number(body.page_index)
);
return new Response(JSON.stringify(search_result), option);
}
@ -322,11 +357,14 @@ async function handleSearch(request, gd) {
* @returns {Promise<Response>} 注意如果从前台接收的id代表的项目不在目标gd盘下那么response会返回给前台一个空字符串""
*/
async function handleId2Path(request, gd) {
const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}};
const option = {
status: 200,
headers: { "Access-Control-Allow-Origin": "*" },
};
let body = await request.text();
body = JSON.parse(body);
let path = await gd.findPathById(body.id);
return new Response(path || '', option);
return new Response(path || "", option);
}
class googleDrive {
@ -346,8 +384,8 @@ class googleDrive {
this.passwords = [];
// id <-> path
this.id_path_cache = {};
this.id_path_cache[this.root['id']] = '/';
this.paths["/"] = this.root['id'];
this.id_path_cache[this.root["id"]] = "/";
this.paths["/"] = this.root["id"];
/*if (this.root['pass'] != "") {
this.passwords['/'] = this.root['pass'];
}*/
@ -370,9 +408,9 @@ class googleDrive {
})();*/
// 等待 user_drive_real_root_id 只获取1次
if (authConfig.user_drive_real_root_id) return;
const root_obj = await (gds[0] || this).findItemById('root');
const root_obj = await (gds[0] || this).findItemById("root");
if (root_obj && root_obj.id) {
authConfig.user_drive_real_root_id = root_obj.id
authConfig.user_drive_real_root_id = root_obj.id;
}
}
@ -381,9 +419,9 @@ class googleDrive {
* @returns {Promise<void>}
*/
async initRootType() {
const root_id = this.root['id'];
const root_id = this.root["id"];
const types = CONSTS.gd_root_type;
if (root_id === 'root' || root_id === authConfig.user_drive_real_root_id) {
if (root_id === "root" || root_id === authConfig.user_drive_real_root_id) {
this.root_type = types.user_drive;
} else {
const obj = await this.getShareDriveObjById(root_id);
@ -397,65 +435,72 @@ class googleDrive {
* @returns {Response|null}
*/
basicAuthResponse(request) {
const user = this.root.user || '',
pass = this.root.pass || '',
_401 = new Response('Unauthorized', {
headers: {'WWW-Authenticate': `Basic realm="goindex:drive:${this.order}"`},
status: 401
const user = this.root.user || "",
pass = this.root.pass || "",
_401 = new Response("Unauthorized", {
headers: {
"WWW-Authenticate": `Basic realm="goindex:drive:${this.order}"`,
},
status: 401,
});
if (user || pass) {
const auth = request.headers.get('Authorization')
const auth = request.headers.get("Authorization");
if (auth) {
try {
const [received_user, received_pass] = atob(auth.split(' ').pop()).split(':');
return (received_user === user && received_pass === pass) ? null : _401;
const [received_user, received_pass] = atob(
auth.split(" ").pop()
).split(":");
return received_user === user && received_pass === pass ? null : _401;
} catch (e) {}
}
} else return null;
return _401;
}
async view(url, range = '', inline = true) {
async view(url, range = "", inline = true) {
let requestOption = await this.requestOption();
requestOption.headers['Range'] = range;
requestOption.headers["Range"] = range;
let res = await fetch(url, requestOption);
const {headers} = res = new Response(res.body, res)
this.authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*');
inline === true && headers.set('Content-Disposition', 'inline');
const { headers } = (res = new Response(res.body, res));
this.authConfig.enable_cors_file_down &&
headers.append("Access-Control-Allow-Origin", "*");
inline === true && headers.set("Content-Disposition", "inline");
return res;
}
async down(id, range = '', inline = false) {
async down(id, range = "", inline = false) {
let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
let requestOption = await this.requestOption();
requestOption.headers['Range'] = range;
requestOption.headers["Range"] = range;
let res = await fetch(url, requestOption);
const {headers} = res = new Response(res.body, res)
this.authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*');
inline === true && headers.set('Content-Disposition', 'inline');
const { headers } = (res = new Response(res.body, res));
this.authConfig.enable_cors_file_down &&
headers.append("Access-Control-Allow-Origin", "*");
inline === true && headers.set("Content-Disposition", "inline");
return res;
}
async file(path) {
if (typeof this.files[path] == 'undefined') {
if (typeof this.files[path] == "undefined") {
this.files[path] = await this._file(path);
}
return this.files[path];
}
async _file(path) {
let arr = path.split('/');
let arr = path.split("/");
let name = arr.pop();
name = decodeURIComponent(name).replace(/\'/g, "\\'");
let dir = arr.join('/') + '/';
let dir = arr.join("/") + "/";
// console.log(name, dir);
let parent = await this.findPathId(dir);
// console.log(parent);
let url = 'https://www.googleapis.com/drive/v3/files';
let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
let url = "https://www.googleapis.com/drive/v3/files";
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
params.q = `'${parent}' in parents and name = '${name}' and trashed = false`;
params.fields = "files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)";
url += '?' + this.enQuery(params);
params.fields =
"files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)";
url += "?" + this.enQuery(params);
let requestOption = await this.requestOption();
let response = await fetch(url, requestOption);
let obj = await response.json();
@ -470,15 +515,16 @@ class googleDrive {
this.path_children_cache = {};
}
if (this.path_children_cache[path]
&& this.path_children_cache[path][page_index]
&& this.path_children_cache[path][page_index].data
if (
this.path_children_cache[path] &&
this.path_children_cache[path][page_index] &&
this.path_children_cache[path][page_index].data
) {
let child_obj = this.path_children_cache[path][page_index];
return {
nextPageToken: child_obj.nextPageToken || null,
curPageIndex: page_index,
data: child_obj.data
data: child_obj.data,
};
}
@ -488,18 +534,17 @@ class googleDrive {
// 对有多页的,进行缓存
if (result.nextPageToken && data.files) {
if (!Array.isArray(this.path_children_cache[path])) {
this.path_children_cache[path] = []
this.path_children_cache[path] = [];
}
this.path_children_cache[path][Number(result.curPageIndex)] = {
nextPageToken: result.nextPageToken,
data: data
data: data,
};
}
return result
return result;
}
async _ls(parent, page_token = null, page_index = 0) {
// console.log("_ls", parent);
@ -507,17 +552,18 @@ class googleDrive {
return null;
}
let obj;
let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
params.q = `'${parent}' in parents and trashed = false AND name !='.password'`;
params.orderBy = 'folder,name,modifiedTime desc';
params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
params.orderBy = "folder,name,modifiedTime desc";
params.fields =
"nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
params.pageSize = this.authConfig.files_list_page_size;
if (page_token) {
params.pageToken = page_token;
}
let url = 'https://www.googleapis.com/drive/v3/files';
url += '?' + this.enQuery(params);
let url = "https://www.googleapis.com/drive/v3/files";
url += "?" + this.enQuery(params);
let requestOption = await this.requestOption();
let response = await fetch(url, requestOption);
obj = await response.json();
@ -525,7 +571,7 @@ class googleDrive {
return {
nextPageToken: obj.nextPageToken || null,
curPageIndex: page_index,
data: obj
data: obj,
};
/*do {
@ -540,7 +586,6 @@ class googleDrive {
files.push(...obj.files);
pageToken = obj.nextPageToken;
} while (pageToken);*/
}
async password(path) {
@ -550,7 +595,7 @@ class googleDrive {
// console.log("load", path, ".password", this.passwords[path]);
let file = await this.file(path + '.password');
let file = await this.file(path + ".password");
if (file == undefined) {
this.passwords[path] = null;
} else {
@ -563,7 +608,6 @@ class googleDrive {
return this.passwords[path];
}
/**
* 通过 id 获取 share drive 信息
* @param any_id
@ -571,7 +615,7 @@ class googleDrive {
*/
async getShareDriveObjById(any_id) {
if (!any_id) return null;
if ('string' !== typeof any_id) return null;
if ("string" !== typeof any_id) return null;
let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`;
let requestOption = await this.requestOption();
@ -579,10 +623,9 @@ class googleDrive {
let obj = await res.json();
if (obj && obj.id) return obj;
return null
return null;
}
/**
* 搜索
* @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>}
@ -595,7 +638,7 @@ class googleDrive {
const empty_result = {
nextPageToken: null,
curPageIndex: page_index,
data: null
data: null,
};
if (!is_user_drive && !is_share_drive) {
@ -607,15 +650,17 @@ class googleDrive {
return empty_result;
}
let words = keyword.split(/\s+/);
let name_search_str = `name contains '${words.join("' AND name contains '")}'`;
let name_search_str = `name contains '${words.join(
"' AND name contains '"
)}'`;
// corpora 为 user 是个人盘 ,为 drive 是团队盘。配合 driveId
let params = {};
if (is_user_drive) {
params.corpora = 'user'
params.corpora = "user";
}
if (is_share_drive) {
params.corpora = 'drive';
params.corpora = "drive";
params.driveId = this.root.id;
// This parameter will only be effective until June 1, 2020. Afterwards shared drive items will be included in the results.
params.includeItemsFromAllDrives = true;
@ -625,12 +670,13 @@ class googleDrive {
params.pageToken = page_token;
}
params.q = `trashed = false AND name !='.password' AND (${name_search_str})`;
params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
params.fields =
"nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
params.pageSize = this.authConfig.search_result_list_page_size;
// params.orderBy = 'folder,name,modifiedTime desc';
let url = 'https://www.googleapis.com/drive/v3/files';
url += '?' + this.enQuery(params);
let url = "https://www.googleapis.com/drive/v3/files";
url += "?" + this.enQuery(params);
// console.log(params)
let requestOption = await this.requestOption();
let response = await fetch(url, requestOption);
@ -639,11 +685,10 @@ class googleDrive {
return {
nextPageToken: res_obj.nextPageToken || null,
curPageIndex: page_index,
data: res_obj
data: res_obj,
};
}
/**
* 一层一层的向上获取这个文件或文件夹的上级文件夹的 file 对象注意会很慢
* 最多向上寻找到当前 gd 对象的根目录 (root id)
@ -696,7 +741,7 @@ class googleDrive {
}
await addItsFirstParent(child_obj);
return meet_top ? parent_files : null
return meet_top ? parent_files : null;
}
/**
@ -710,20 +755,27 @@ class googleDrive {
}
const p_files = await this.findParentFilesRecursion(child_id);
if (!p_files || p_files.length < 1) return '';
if (!p_files || p_files.length < 1) return "";
let cache = [];
// 把查出来的每一级的path和id都缓存一下
p_files.forEach((value, idx) => {
const is_folder = idx === 0 ? (p_files[idx].mimeType === CONSTS.folder_mime_type) : true;
let path = '/' + p_files.slice(idx).map(it => it.name).reverse().join('/');
if (is_folder) path += '/';
cache.push({id: p_files[idx].id, path: path})
const is_folder =
idx === 0 ? p_files[idx].mimeType === CONSTS.folder_mime_type : true;
let path =
"/" +
p_files
.slice(idx)
.map((it) => it.name)
.reverse()
.join("/");
if (is_folder) path += "/";
cache.push({ id: p_files[idx].id, path: path });
});
cache.forEach((obj) => {
this.id_path_cache[obj.id] = obj.path;
this.paths[obj.path] = obj.id
this.paths[obj.path] = obj.id;
});
/*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type;
@ -733,25 +785,26 @@ class googleDrive {
return cache[0].path;
}
// 根据id获取file item
async findItemById(id) {
const is_user_drive = this.root_type === CONSTS.gd_root_type.user_drive;
let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${CONSTS.default_file_fields}${is_user_drive ? '' : '&supportsAllDrives=true'}`;
let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${
CONSTS.default_file_fields
}${is_user_drive ? "" : "&supportsAllDrives=true"}`;
let requestOption = await this.requestOption();
let res = await fetch(url, requestOption);
return await res.json()
return await res.json();
}
async findPathId(path) {
let c_path = '/';
let c_path = "/";
let c_id = this.paths[c_path];
let arr = path.trim('/').split('/');
let arr = path.trim("/").split("/");
for (let name of arr) {
c_path += name + '/';
c_path += name + "/";
if (typeof this.paths[c_path] == 'undefined') {
if (typeof this.paths[c_path] == "undefined") {
let id = await this._findDirId(c_id, name);
this.paths[c_path] = id;
}
@ -774,11 +827,11 @@ class googleDrive {
return null;
}
let url = 'https://www.googleapis.com/drive/v3/files';
let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
let url = "https://www.googleapis.com/drive/v3/files";
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`;
params.fields = "nextPageToken, files(id, name, mimeType)";
url += '?' + this.enQuery(params);
url += "?" + this.enQuery(params);
let requestOption = await this.requestOption();
let response = await fetch(url, requestOption);
let obj = await response.json();
@ -790,7 +843,10 @@ class googleDrive {
async accessToken() {
console.log("accessToken");
if (this.authConfig.expires == undefined || this.authConfig.expires < Date.now()) {
if (
this.authConfig.expires == undefined ||
this.authConfig.expires < Date.now()
) {
const obj = await this.fetchAccessToken();
if (obj.access_token != undefined) {
this.authConfig.accessToken = obj.access_token;
@ -804,19 +860,19 @@ class googleDrive {
console.log("fetchAccessToken");
const url = "https://www.googleapis.com/oauth2/v4/token";
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
"Content-Type": "application/x-www-form-urlencoded",
};
const post_data = {
'client_id': this.authConfig.client_id,
'client_secret': this.authConfig.client_secret,
'refresh_token': this.authConfig.refresh_token,
'grant_type': 'refresh_token'
}
client_id: this.authConfig.client_id,
client_secret: this.authConfig.client_secret,
refresh_token: this.authConfig.refresh_token,
grant_type: "refresh_token",
};
let requestOption = {
'method': 'POST',
'headers': headers,
'body': this.enQuery(post_data)
method: "POST",
headers: headers,
body: this.enQuery(post_data),
};
const response = await fetch(url, requestOption);
@ -836,36 +892,39 @@ class googleDrive {
return response;
}
async requestOption(headers = {}, method = 'GET') {
async requestOption(headers = {}, method = "GET") {
const accessToken = await this.accessToken();
headers['authorization'] = 'Bearer ' + accessToken;
return {'method': method, 'headers': headers};
headers["authorization"] = "Bearer " + accessToken;
return { method: method, headers: headers };
}
enQuery(data) {
const ret = [];
for (let d in data) {
ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
}
return ret.join('&');
return ret.join("&");
}
sleep(ms) {
return new Promise(function(resolve, reject) {
let i = 0;
setTimeout(function() {
console.log('sleep' + ms);
console.log("sleep" + ms);
i++;
if (i >= 2) reject(new Error('i>=2'));
if (i >= 2) reject(new Error("i>=2"));
else resolve(i);
}, ms);
})
});
}
}
String.prototype.trim = function(char) {
if (char) {
return this.replace(new RegExp('^\\' + char + '+|\\' + char + '+$', 'g'), '');
return this.replace(
new RegExp("^\\" + char + "+|\\" + char + "+$", "g"),
""
);
}
return this.replace(/^\s+|\s+$/g, '');
return this.replace(/^\s+|\s+$/g, "");
};

2
package-lock.json generated
View File

@ -12677,7 +12677,7 @@
},
"webpack-bundle-analyzer": {
"version": "3.7.0",
"resolved": "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-3.7.0.tgz?cache=0&sync_timestamp=1586846559504&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-bundle-analyzer%2Fdownload%2Fwebpack-bundle-analyzer-3.7.0.tgz",
"resolved": "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-3.7.0.tgz?cache=0&sync_timestamp=1586846510646&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-bundle-analyzer%2Fdownload%2Fwebpack-bundle-analyzer-3.7.0.tgz",
"integrity": "sha1-hNpDTolEKJm4hNmtOORm0NsCpW8=",
"dev": true,
"requires": {

View File

@ -14,7 +14,7 @@
<title><%= htmlWebpackPlugin.options.title %></title>
<script>
var authConfig = {
version: '1.0.0',
version: '1.1.0',
roots: [
{
id: "0AEofxddwF4bAUk9PVA",
@ -26,18 +26,31 @@
name: "PriveDrive",
pass: "",
},
{
id: "1ZUli0boWXTpAedKDzE_IF9CWT10G6V86",
name: "folder1",
pass: "",
}
],
};
var themeOptions = {
// en/zh-chs/zh-cht
languages: 'en'
languages: 'en',
render: {
head_md: false,
readme_md: false,
// 是否显示文件/文件夹描述(默认不显示)
// Show file/folder description or not (not shown by default)
desc: false,
}
}
window.gdconfig = JSON.parse(JSON.stringify({ version: authConfig.version, themeOptions: themeOptions }));
window.themeOptions = themeOptions;
window.gds = JSON.parse(
JSON.stringify(authConfig.roots.map((it) => it.name))
);
window.current_drive_order = 0;
// window.MODEL = { q: "the" };
// window.MODEL = { q: "the",root_type: 1 };
</script>
<!-- 使用 CDN 加速的 JS 文件,配置在 vue.config.js 下 -->
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>

View File

@ -1,37 +1,34 @@
import axios from "@utils/axios";
let Base64 = require("js-base64").Base64;
// const exts = [
// "html",
// "php",
// "css",
// "go",
// "java",
// "js",
// "json",
// "py",
// "txt",
// "sh",
// "md",
// "mp4",
// "webm",
// "mkv",
// "bmp",
// "jpg",
// "jpeg",
// "png",
// "gif",
// ];
const text_exts = [
"html",
"php",
"css",
"go",
"java",
"js",
"json",
"txt",
"sh",
"md",
];
const video_exts = ["mp4", "webm", "mkv", "m3u8"];
const image_exts = ["bmp", "jpg", "jpeg", "png", "gif"];
const pdf_exts = ["pdf"];
export const encodePath = (path) => {
return path.replace(/(.*)/, (p1, p2) => {
return p2.replace().replace(/\//g, "%2F").replace(/#/g, "%23")
})
return p2
.replace()
.replace(/\//g, "%2F")
.replace(/#/g, "%23");
});
//return path.replace().replace("/", "%2F").replace("#", "%23")
}
};
export const checkoutPath = (path, file) => {
path = encodePath(path)
path = encodePath(path);
if (file.mimeType === "application/vnd.google-apps.folder") {
if (path.substr(-1) !== "/") {
path += "/";
@ -47,25 +44,25 @@ export const checkView = (path) => {
.pop()
.toLowerCase();
let base64Path = encode64(path);
if ("|html|php|css|go|java|js|json|txt|sh|md|".indexOf(`|${ext}|`) >= 0) {
if (text_exts.indexOf(`${ext}`) != -1) {
path = path.replace(/\/(\d+:)\/.*/, (p1, p2) => {
return `/${p2}text/${base64Path}`;
});
}
if ("|pdf|".indexOf(`|${ext}|`) >= 0) {
if (pdf_exts.indexOf(`${ext}`) != -1) {
path = path.replace(/\/(\d+:)\/.*/, (p1, p2) => {
return `/${p2}pdf/${base64Path}`;
});
}
if ("|mp4|webm|mkv|".indexOf(`|${ext}|`) >= 0) {
if (video_exts.indexOf(`${ext}`) != -1) {
path = path.replace(/\/(\d+:)\/.*/, (p1, p2) => {
return `/${p2}video/${base64Path}`;
});
}
if ("|bmp|jpg|jpeg|png|gif|".indexOf(`|${ext}|`) >= 0) {
if (image_exts.indexOf(`${ext}`) != -1) {
path = path.replace(/\/(\d+:)\/.*/, (p1, p2) => {
return `/${p2}image/${base64Path}`;
});
@ -73,27 +70,16 @@ export const checkView = (path) => {
return path;
};
export const getQueryString = (path, param) => {
if (!path) {
return "";
}
var args = getURLParameters(path);
return args[param] ? args[param] : "";
export const checkExtends = (path) => {
let name = path.split("/").pop();
let ext = name
.split(".")
.pop()
.toLowerCase();
let exts = text_exts.concat(...video_exts,...image_exts,...pdf_exts);
return exts.indexOf(`${ext}`) != -1;
};
export const getURLParameters = (url) =>
url
.match(/([^?=&]+)(=([^&]*))/g)
.reduce(
(a, v) => (
(a[v.slice(0, v.indexOf("="))] = v.slice(v.indexOf("=") + 1)), a
), {}
);
// console.log(getURLParameters("/Movies/xx.mp4?a=view&y=123"));
//console.log(getQueryString("/Movies/xx.mp4?a=view&y=123", "y"));
export const encode64 = (str) => {
return Base64.encodeURI(str);
};
@ -182,7 +168,6 @@ export function formatFileSize(bytes) {
return bytes;
}
/**
* @param {Number String Date}
* @param {String} 'YYYY-MM-DD HH:mm:ss EEE' (Y)(M)(D)12小时(h)24小时(H)(m)(s)毫秒(S)(E)季度(q)
@ -190,42 +175,55 @@ export function formatFileSize(bytes) {
* @example XDate.format(new Date(), "YYYY-MM-DD") ==> 2017-08-23
*/
export function formatDate(date, fmt) {
fmt = fmt || 'YYYY-MM-DD HH:mm:ss';
if (typeof date === 'string') {
fmt = fmt || "YYYY-MM-DD HH:mm:ss";
if (typeof date === "string") {
// date = new Date(date.replace(/-/g, '/'))
date = new Date(date)
date = new Date(date);
}
if (typeof date === 'number') {
date = new Date(date)
if (typeof date === "number") {
date = new Date(date);
}
var o = {
'M+': date.getMonth() + 1,
'D+': date.getDate(),
'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
'S': date.getMilliseconds()
}
"M+": date.getMonth() + 1,
"D+": date.getDate(),
"h+": date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
};
var week = {
'0': '\u65e5',
'1': '\u4e00',
'2': '\u4e8c',
'3': '\u4e09',
'4': '\u56db',
'5': '\u4e94',
'6': '\u516d'
}
"0": "\u65e5",
"1": "\u4e00",
"2": "\u4e8c",
"3": "\u4e09",
"4": "\u56db",
"5": "\u4e94",
"6": "\u516d",
};
if (/(Y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
if (/(E+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + week[date.getDay() + ''])
fmt = fmt.replace(
RegExp.$1,
(RegExp.$1.length > 1
? RegExp.$1.length > 2
? "\u661f\u671f"
: "\u5468"
: "") + week[date.getDay() + ""]
);
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;

View File

@ -108,6 +108,13 @@ export default {
// height: 100%;
padding: 10px 0.75em;
}
.level-left {
width: 95%;
.level-item {
display: initial;
width: 100%;
}
}
.level-right {
.level-item {
// border-radius: 50%;

View File

@ -42,7 +42,7 @@
<div class="navbar-end">
<!-- is-hidden-desktop -->
<div class="navbar-item">
<div class="navbar-item" v-show="showSearch">
<div class="field is-grouped">
<p class="control has-icons-left is-dark" style="width:100%;">
<input
@ -71,7 +71,10 @@
<i class="fab fa-github"></i>
</a>
<header-setting />
<a class="navbar-item is-hidden-desktop" @click.stop="$refs.viewMode.toggleMode">
<a
class="navbar-item is-hidden-desktop"
@click.stop="$refs.viewMode.toggleMode"
>
<view-mode ref="viewMode" />
</a>
</div>
@ -144,6 +147,10 @@ export default {
getCurrGD() {
return this.gds.filter((item) => item.name !== this.currgd.name);
},
showSearch() {
//
return window.MODEL ? window.MODEL.root_type < 2 : true
},
},
watch: {
"$route.params.id": "chooseGD",

View File

@ -1,6 +1,6 @@
<template>
<div>
<headmd :option="headmd" v-if="headmd.display"></headmd>
<headmd :option="headmd" v-if="renderHeadMD && headmd.display"></headmd>
<div
class="golist"
v-loading="loading"
@ -8,7 +8,13 @@
infinite-scroll-disabled="busy"
infinite-scroll-distance="10"
>
<list-view :data="buildFiles" v-if="mode === 'list'" :icons="getIcon" :go="go" :copy="copy" />
<list-view
:data="buildFiles"
v-if="mode === 'list'"
:icons="getIcon"
:go="go"
:copy="copy"
/>
<grid-view
class="g2-content"
:data="buildFiles"
@ -17,7 +23,10 @@
:go="go"
:thum="thum"
/>
<div v-show="files.length === 0" class="has-text-centered no-content"></div>
<div
v-show="files.length === 0"
class="has-text-centered no-content"
></div>
<center>
<div :class="!busy ? 'is-hidden' : ''">
<i class="fa fa-spinner fa-pulse fa-2x fa-fw"></i>
@ -28,8 +37,13 @@
</span>-->
</center>
</div>
<div class="is-divider" :data-content="$t('list.total')+' '+files.length+' ' + $t('list.item')"></div>
<readmemd :option="readmemd" v-if="readmemd.display"></readmemd>
<div
class="is-divider"
:data-content="
$t('list.total') + ' ' + files.length + ' ' + $t('list.item')
"
></div>
<readmemd :option="readmemd" v-if="renderReadMeMD && readmemd.display"></readmemd>
<viewer
v-if="viewer && images && images.length > 0"
@ -57,6 +71,7 @@ import {
formatFileSize,
checkoutPath,
checkView,
checkExtends,
} from "@utils/AcrouUtil";
import axios from "@/utils/axios";
import { mapState } from "vuex";
@ -139,6 +154,12 @@ export default {
(file) => file.mimeType.indexOf("image") != -1
);
},
renderHeadMD() {
return window.themeOptions.render.head_md || false;
},
renderReadMeMD() {
return window.themeOptions.render.readme_md || false;
},
},
created() {
this.render();
@ -238,7 +259,7 @@ export default {
if (file.mimeType.indexOf("image") != -1) {
this.viewer = true;
this.$nextTick(() => {
let index = this.images.findIndex(item => item.path === file.path)
let index = this.images.findIndex((item) => item.path === file.path);
this.$viewer.view(index);
});
return;
@ -253,7 +274,7 @@ export default {
window.open(path);
return;
}
if (target === "down") {
if (target === "down" || (!checkExtends(path) && !file.isFolder)) {
let temp_path = this.$route.params.path ? this.$route.params.path : "";
location.href = `/${this.$route.params.id}:down/${temp_path}/${file.name}`;
return;

View File

@ -32,7 +32,11 @@
<use :xlink:href="icons(file.mimeType)" />
</svg>
{{ file.name }}
<span class="has-text-grey g2-file-desc" v-html="file.description"></span>
<span
class="has-text-grey g2-file-desc"
v-if="isShowDesc"
v-html="file.description"
></span>
</td>
<td class="is-hidden-mobile is-hidden-touch">
{{ file.modifiedTime }}
@ -107,6 +111,9 @@ export default {
},
];
},
isShowDesc() {
return window.themeOptions.render.desc || false;
},
},
};
</script>