diff --git a/go2index/index.js b/go2index/index.js index 5127c2f..19a2d9f 100644 --- a/go2index/index.js +++ b/go2index/index.js @@ -1,91 +1,98 @@ var authConfig = { - "siteName": "Achirou's Cloud", // 网站名称 - "version" : "1.1.0", // 程序版本 - "theme" : "acrou", - /*"client_id": "202264815644.apps.googleusercontent.com", + 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 - /** - * 设置要显示的多个云端硬盘;按格式添加多个 - * [id]: 可以是 团队盘id、子文件夹id、或者"root"(代表个人盘根目录); - * [name]: 显示的名称 - * [user]: Basic Auth 的用户名 - * [pass]: Basic Auth 的密码 - * [protect_file_link]: Basic Auth 是否用于保护文件链接,默认值(不设置时)为 false,即不保护文件链接(方便 直链下载/外部播放 等) - * 每个盘的 Basic Auth 都可以单独设置。Basic Auth 默认保护该盘下所有文件夹/子文件夹路径 - * 【注意】默认不保护文件链接,这样可以方便 直链下载/外部播放; - * 如果要保护文件链接,需要将 protect_file_link 设置为 true,此时如果要进行外部播放等操作,需要将 host 替换为 user:pass@host 的 形式 - * 不需要 Basic Auth 的盘,保持 user 和 pass 同时为空即可。(直接不设置也可以) - * 【注意】对于id设置为为子文件夹id的盘将不支持搜索功能(不影响其他盘)。 - */ - "roots": [ - { - id: "", - name: "TeamDrive", - pass: "", - }, - { - id: "root", - name: "PrivateDrive", - user: '', - pass: "", - protect_file_link: true - }, - { - id: "", - name: "folder1", - pass: "", - } - ], - "default_gd": 0, - /** - * 文件列表页面每页显示的数量。【推荐设置值为 100 到 1000 之间】; - * 如果设置大于1000,会导致请求 drive api 时出错; - * 如果设置的值过小,会导致文件列表页面滚动条增量加载(分页加载)失效; - * 此值的另一个作用是,如果目录内文件数大于此设置值(即需要多页展示的),将会对首次列目录结果进行缓存。 - */ - "files_list_page_size": 50, - /** - * 搜索结果页面每页显示的数量。【推荐设置值为 50 到 1000 之间】; - * 如果设置大于1000,会导致请求 drive api 时出错; - * 如果设置的值过小,会导致搜索结果页面滚动条增量加载(分页加载)失效; - * 此值的大小影响搜索操作的响应速度。 - */ - "search_result_list_page_size": 50, - // 确认有 cors 用途的可以开启 - "enable_cors_file_down": false, - /** - * 上面的 basic auth 已经包含了盘内全局保护的功能。所以默认不再去认证 .password 文件内的密码; - * 如果在全局认证的基础上,仍需要给某些目录单独进行 .password 文件内的密码验证的话,将此选项设置为 true; - * 【注意】如果开启了 .password 文件密码验证,每次列目录都会额外增加查询目录内 .password 文件是否存在的开销。 - */ - "enable_password_file_verify": true + // 强烈推荐使用自己的 client_id 和 client_secret + client_id: "", + client_secret: "", + refresh_token: "", // 授权 token + /** + * 设置要显示的多个云端硬盘;按格式添加多个 + * [id]: 可以是 团队盘id、子文件夹id、或者"root"(代表个人盘根目录); + * [name]: 显示的名称 + * [user]: Basic Auth 的用户名 + * [pass]: Basic Auth 的密码 + * [protect_file_link]: Basic Auth 是否用于保护文件链接,默认值(不设置时)为 false,即不保护文件链接(方便 直链下载/外部播放 等) + * 每个盘的 Basic Auth 都可以单独设置。Basic Auth 默认保护该盘下所有文件夹/子文件夹路径 + * 【注意】默认不保护文件链接,这样可以方便 直链下载/外部播放; + * 如果要保护文件链接,需要将 protect_file_link 设置为 true,此时如果要进行外部播放等操作,需要将 host 替换为 user:pass@host 的 形式 + * 不需要 Basic Auth 的盘,保持 user 和 pass 同时为空即可。(直接不设置也可以) + * 【注意】对于id设置为为子文件夹id的盘将不支持搜索功能(不影响其他盘)。 + */ + roots: [ + { + id: "", + name: "TeamDrive", + pass: "", + }, + { + id: "root", + name: "PrivateDrive", + user: "", + pass: "", + protect_file_link: true, + }, + { + id: "", + name: "folder1", + pass: "", + }, + ], + default_gd: 0, + /** + * 文件列表页面每页显示的数量。【推荐设置值为 100 到 1000 之间】; + * 如果设置大于1000,会导致请求 drive api 时出错; + * 如果设置的值过小,会导致文件列表页面滚动条增量加载(分页加载)失效; + * 此值的另一个作用是,如果目录内文件数大于此设置值(即需要多页展示的),将会对首次列目录结果进行缓存。 + */ + files_list_page_size: 50, + /** + * 搜索结果页面每页显示的数量。【推荐设置值为 50 到 1000 之间】; + * 如果设置大于1000,会导致请求 drive api 时出错; + * 如果设置的值过小,会导致搜索结果页面滚动条增量加载(分页加载)失效; + * 此值的大小影响搜索操作的响应速度。 + */ + search_result_list_page_size: 50, + // 确认有 cors 用途的可以开启 + enable_cors_file_down: false, + /** + * 上面的 basic auth 已经包含了盘内全局保护的功能。所以默认不再去认证 .password 文件内的密码; + * 如果在全局认证的基础上,仍需要给某些目录单独进行 .password 文件内的密码验证的话,将此选项设置为 true; + * 【注意】如果开启了 .password 文件密码验证,每次列目录都会额外增加查询目录内 .password 文件是否存在的开销。 + */ + enable_password_file_verify: true, }; var themeOptions = { - //可选默认系统语言:en/zh-chs/zh-cht - languages: 'en' -} + //可选默认系统语言:en/zh-chs/zh-cht + languages: "en", + render: { + head_md: false, + readme_md: false, + // 是否显示文件/文件夹描述(默认不显示) + // Show file/folder description or not (not shown by default) + desc: false + }, +}; /** * global functions */ const FUNCS = { - /** - * 转换成针对谷歌搜索词法相对安全的搜索关键词 - */ - formatSearchKeyword: function (keyword) { - let nothing = ""; - let space = " "; - if (!keyword) return nothing; - return keyword.replace(/(!=)|['"=<>/\\:]/g, nothing) - .replace(/[,,|(){}]/g, space) - .trim() - } - + /** + * 转换成针对谷歌搜索词法相对安全的搜索关键词 + */ + formatSearchKeyword: function(keyword) { + let nothing = ""; + let space = " "; + if (!keyword) return nothing; + return keyword + .replace(/(!=)|['"=<>/\\:]/g, nothing) + .replace(/[,,|(){}]/g, space) + .trim(); + }, }; /** @@ -93,21 +100,21 @@ 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'; - gd_root_type = { - user_drive: 0, - share_drive: 1, - sub_folder: 2 - }; - folder_mime_type = 'application/vnd.google-apps.folder'; + default_file_fields = + "parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size"; + gd_root_type = { + user_drive: 0, + share_drive: 1, + sub_folder: 2, + }; + folder_mime_type = "application/vnd.google-apps.folder"; })(); - // gd instances var gds = []; function html(current_drive_order = 0, model = {}) { - return ` + return ` @@ -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); @@ -130,10 +143,10 @@ function html(current_drive_order = 0, model = {}) { `; -}; +} -addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)); +addEventListener("fetch", (event) => { + event.respondWith(handleRequest(event.request)); }); /** @@ -141,178 +154,200 @@ addEventListener('fetch', event => { * @param {Request} request */ async function handleRequest(request) { - if (gds.length === 0) { - for (let i = 0; i < authConfig.roots.length; i++) { - const gd = new googleDrive(authConfig, i); - await gd.init(); - gds.push(gd) - } - // 这个操作并行,提高效率 - let tasks = []; - gds.forEach(gd => { - tasks.push(gd.initRootType()); - }); - for (let task of tasks) { - await task; - } + if (gds.length === 0) { + for (let i = 0; i < authConfig.roots.length; i++) { + const gd = new googleDrive(authConfig, i); + await gd.init(); + gds.push(gd); } - - // 从 path 中提取 drive order - // 并根据 drive order 获取对应的 gd instance - let gd; - let url = new URL(request.url); - let path = url.pathname; - - /** - * 重定向至起始页 - * @returns {Response} - */ - function redirectToIndexPage() { - return new Response('', {status: 301, headers: {'Location': `/${authConfig.default_gd}:/`}}); + // 这个操作并行,提高效率 + let tasks = []; + gds.forEach((gd) => { + tasks.push(gd.initRootType()); + }); + for (let task of tasks) { + await task; } + } - if (path == '/') return redirectToIndexPage(); - if (path.toLowerCase() == '/favicon.ico') { - // 后面可以找一个 favicon - return new Response('', {status: 404}) - } + // 从 path 中提取 drive order + // 并根据 drive order 获取对应的 gd instance + let gd; + let url = new URL(request.url); + let path = url.pathname; - // 特殊命令格式 - const command_reg = /^\/(?\d+):(?[a-zA-Z0-9]+)(\/.*)?$/g; - const match = command_reg.exec(path); - let command; - if (match) { - const num = match.groups.num; - const order = Number(num); - if (order >= 0 && order < gds.length) { - gd = gds[order]; - } else { - return redirectToIndexPage() - } - // basic auth - for (const r = gd.basicAuthResponse(request); r;) return r; - command = match.groups.command; + /** + * 重定向至起始页 + * @returns {Response} + */ + function redirectToIndexPage() { + return new Response("", { + status: 301, + headers: { Location: `/${authConfig.default_gd}:/` }, + }); + } - // 搜索 - 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") || '', - is_search_page: true, - root_type: gd.root_type - }), - { - status: 200, - headers: {'Content-Type': 'text/html; charset=utf-8'} - }); - } - } 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 new Response(html(gd.order, {root_type: gd.root_type}), { - status: 200, - headers: {'Content-Type': 'text/html; charset=utf-8'} - }); - } - } - const reg = new RegExp(`^(/\\d+:)${command}/`, 'g'); - path = path.replace(reg,(p1,p2)=>{ - return p2+'/' - }) - // 期望的 path 格式 - const common_reg = /^\/\d+:\/.*$/g; - try { - if (!path.match(common_reg)) { - return redirectToIndexPage(); - } - let split = path.split("/"); - let order = Number(split[1].slice(0, -1)); - if (order >= 0 && order < gds.length) { - gd = gds[order]; - } else { - return redirectToIndexPage() - } - } catch (e) { - return redirectToIndexPage() - } + if (path == "/") return redirectToIndexPage(); + if (path.toLowerCase() == "/favicon.ico") { + // 后面可以找一个 favicon + return new Response("", { status: 404 }); + } - // 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') { - return basic_auth_res || apiRequest(request, gd); - } - - 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}), { - status: 200, - headers: {'Content-Type': 'text/html; charset=utf-8'} - }); + // 特殊命令格式 + const command_reg = /^\/(?\d+):(?[a-zA-Z0-9]+)(\/.*)?$/g; + const match = command_reg.exec(path); + let command; + if (match) { + const num = match.groups.num; + const order = Number(num); + if (order >= 0 && order < gds.length) { + gd = gds[order]; } else { - 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'); - if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res; - const is_down = !(command && command=='down'); - return gd.down(file.id, range, is_down); + return redirectToIndexPage(); } + // basic auth + for (const r = gd.basicAuthResponse(request); r; ) return r; + command = match.groups.command; + + // 搜索 + 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") || "", + is_search_page: true, + root_type: gd.root_type, + }), + { + status: 200, + headers: { "Content-Type": "text/html; charset=utf-8" }, + } + ); + } + } 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 new Response(html(gd.order, { root_type: gd.root_type }), { + status: 200, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); + } + } + const reg = new RegExp(`^(/\\d+:)${command}/`, "g"); + path = path.replace(reg, (p1, p2) => { + return p2 + "/"; + }); + // 期望的 path 格式 + const common_reg = /^\/\d+:\/.*$/g; + try { + if (!path.match(common_reg)) { + return redirectToIndexPage(); + } + let split = path.split("/"); + let order = Number(split[1].slice(0, -1)); + if (order >= 0 && order < gds.length) { + gd = gds[order]; + } else { + return redirectToIndexPage(); + } + } catch (e) { + 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") { + return basic_auth_res || apiRequest(request, gd); + } + + 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 }), { + status: 200, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }) + ); + } else { + 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"); + if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res; + 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, '') || '/'; + let url = new URL(request.url); + let path = url.pathname; + 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) == '/') { - 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)); + 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) + ); - // check .password file, if `enable_password_file_verify` is true - if (authConfig['enable_password_file_verify']) { - let password = await gd.password(path); - // console.log("dir password", password); - if (password && password.replace("\n", "") !== body.password) { - let html = `{"error": {"code": 401,"message": "password error."}}`; - return new Response(html, option); - } - } - - let list_result = await deferred_list_result; - return new Response(JSON.stringify(list_result), option); - } else { - let file = await gd.file(path); - let range = request.headers.get('Range'); - return new Response(JSON.stringify(file)); + // check .password file, if `enable_password_file_verify` is true + if (authConfig["enable_password_file_verify"]) { + let password = await gd.password(path); + // console.log("dir password", password); + if (password && password.replace("\n", "") !== body.password) { + let html = `{"error": {"code": 401,"message": "password error."}}`; + return new Response(html, option); + } } + + let list_result = await deferred_list_result; + return new Response(JSON.stringify(list_result), option); + } else { + let file = await gd.file(path); + 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': '*'}}; - let body = await request.text(); - body = JSON.parse(body); - let search_result = await - gd.search(body.q || '', body.page_token, Number(body.page_index)); - return new Response(JSON.stringify(search_result), option); + 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) + ); + return new Response(JSON.stringify(search_result), option); } /** @@ -322,45 +357,48 @@ async function handleSearch(request, gd) { * @returns {Promise} 【注意】如果从前台接收的id代表的项目不在目标gd盘下,那么response会返回给前台一个空字符串"" */ async function handleId2Path(request, gd) { - 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); + 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); } class googleDrive { - constructor(authConfig, order) { - // 每个盘对应一个order,对应一个gd实例 - this.order = order; - this.root = authConfig.roots[order]; - this.root.protect_file_link = this.root.protect_file_link || false; - this.url_path_prefix = `/${order}:`; - this.authConfig = authConfig; - // TODO: 这些缓存的失效刷新策略,后期可以制定一下 - // path id - this.paths = []; - // path file - this.files = []; - // path pass - this.passwords = []; - // id <-> path - this.id_path_cache = {}; - this.id_path_cache[this.root['id']] = '/'; - this.paths["/"] = this.root['id']; - /*if (this.root['pass'] != "") { + constructor(authConfig, order) { + // 每个盘对应一个order,对应一个gd实例 + this.order = order; + this.root = authConfig.roots[order]; + this.root.protect_file_link = this.root.protect_file_link || false; + this.url_path_prefix = `/${order}:`; + this.authConfig = authConfig; + // TODO: 这些缓存的失效刷新策略,后期可以制定一下 + // path id + this.paths = []; + // path file + this.files = []; + // path pass + this.passwords = []; + // id <-> path + this.id_path_cache = {}; + this.id_path_cache[this.root["id"]] = "/"; + this.paths["/"] = this.root["id"]; + /*if (this.root['pass'] != "") { this.passwords['/'] = this.root['pass']; }*/ - // this.init(); - } + // this.init(); + } - /** - * 初次授权;然后获取 user_drive_real_root_id - * @returns {Promise} - */ - async init() { - await this.accessToken(); - /*await (async () => { + /** + * 初次授权;然后获取 user_drive_real_root_id + * @returns {Promise} + */ + async init() { + await this.accessToken(); + /*await (async () => { // 只获取1次 if (authConfig.user_drive_real_root_id) return; const root_obj = await (gds[0] || this).findItemById('root'); @@ -368,167 +406,175 @@ class googleDrive { authConfig.user_drive_real_root_id = root_obj.id } })();*/ - // 等待 user_drive_real_root_id ,只获取1次 - if (authConfig.user_drive_real_root_id) return; - const root_obj = await (gds[0] || this).findItemById('root'); - if (root_obj && root_obj.id) { - authConfig.user_drive_real_root_id = root_obj.id - } + // 等待 user_drive_real_root_id ,只获取1次 + if (authConfig.user_drive_real_root_id) return; + const root_obj = await (gds[0] || this).findItemById("root"); + if (root_obj && root_obj.id) { + authConfig.user_drive_real_root_id = root_obj.id; } + } - /** - * 获取根目录类型,设置到 root_type - * @returns {Promise} - */ - async initRootType() { - const root_id = this.root['id']; - const types = CONSTS.gd_root_type; - 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); - this.root_type = obj ? types.share_drive : types.sub_folder; - } + /** + * 获取根目录类型,设置到 root_type + * @returns {Promise} + */ + async initRootType() { + const root_id = this.root["id"]; + const types = CONSTS.gd_root_type; + 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); + this.root_type = obj ? types.share_drive : types.sub_folder; } + } - /** - * Returns a response that requires authorization, or null - * @param request - * @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 - }); - if (user || pass) { - 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; - } catch (e) {} - } - } else return null; - return _401; - } - - async view(url, range = '', inline = true) { - let requestOption = await this.requestOption(); - 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'); - return res; - } - - 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; - 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'); - return res; + /** + * Returns a response that requires authorization, or null + * @param request + * @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, + }); + if (user || pass) { + 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; + } catch (e) {} } + } else return null; + return _401; + } - async file(path) { - if (typeof this.files[path] == 'undefined') { - this.files[path] = await this._file(path); - } - return this.files[path]; + async view(url, range = "", inline = true) { + let requestOption = await this.requestOption(); + 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"); + return res; + } + + 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; + 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"); + return res; + } + + async file(path) { + 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 name = arr.pop(); + name = decodeURIComponent(name).replace(/\'/g, "\\'"); + 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 }; + 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); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let obj = await response.json(); + // console.log(obj); + return obj.files[0]; + } + + // 通过reqeust cache 来缓存 + async list(path, page_token = null, page_index = 0) { + if (this.path_children_cache == undefined) { + // { :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...} + this.path_children_cache = {}; } - async _file(path) { - let arr = path.split('/'); - let name = arr.pop(); - name = decodeURIComponent(name).replace(/\'/g, "\\'"); - 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}; - 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); - let requestOption = await this.requestOption(); - let response = await fetch(url, requestOption); - let obj = await response.json(); - // console.log(obj); - return obj.files[0]; + 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, + }; } - // 通过reqeust cache 来缓存 - async list(path, page_token = null, page_index = 0) { - if (this.path_children_cache == undefined) { - // { :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...} - 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 - ) { - let child_obj = this.path_children_cache[path][page_index]; - return { - nextPageToken: child_obj.nextPageToken || null, - curPageIndex: page_index, - data: child_obj.data - }; - } - - let id = await this.findPathId(path); - let result = await this._ls(id, page_token, page_index); - let data = result.data; - // 对有多页的,进行缓存 - if (result.nextPageToken && data.files) { - if (!Array.isArray(this.path_children_cache[path])) { - this.path_children_cache[path] = [] - } - this.path_children_cache[path][Number(result.curPageIndex)] = { - nextPageToken: result.nextPageToken, - data: data - }; - } - - return result + let id = await this.findPathId(path); + let result = await this._ls(id, page_token, page_index); + let data = result.data; + // 对有多页的,进行缓存 + if (result.nextPageToken && data.files) { + if (!Array.isArray(this.path_children_cache[path])) { + this.path_children_cache[path] = []; + } + this.path_children_cache[path][Number(result.curPageIndex)] = { + nextPageToken: result.nextPageToken, + data: data, + }; } + return result; + } - async _ls(parent, page_token = null, page_index = 0) { - // console.log("_ls", parent); + async _ls(parent, page_token = null, page_index = 0) { + // console.log("_ls", parent); - if (parent == undefined) { - return null; - } - let obj; - 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.pageSize = this.authConfig.files_list_page_size; + if (parent == undefined) { + return null; + } + let obj; + 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.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 requestOption = await this.requestOption(); - let response = await fetch(url, requestOption); - obj = await response.json(); + if (page_token) { + params.pageToken = page_token; + } + 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(); - return { - nextPageToken: obj.nextPageToken || null, - curPageIndex: page_index, - data: obj - }; + return { + nextPageToken: obj.nextPageToken || null, + curPageIndex: page_index, + data: obj, + }; - /*do { + /*do { if (pageToken) { params.pageToken = pageToken; } @@ -540,332 +586,345 @@ class googleDrive { files.push(...obj.files); pageToken = obj.nextPageToken; } while (pageToken);*/ + } + async password(path) { + if (this.passwords[path] !== undefined) { + return this.passwords[path]; } - async password(path) { - if (this.passwords[path] !== undefined) { - return this.passwords[path]; - } + // console.log("load", path, ".password", this.passwords[path]); - // console.log("load", path, ".password", this.passwords[path]); - - let file = await this.file(path + '.password'); - if (file == undefined) { - this.passwords[path] = null; - } else { - let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`; - let requestOption = await this.requestOption(); - let response = await this.fetch200(url, requestOption); - this.passwords[path] = await response.text(); - } - - return this.passwords[path]; + let file = await this.file(path + ".password"); + if (file == undefined) { + this.passwords[path] = null; + } else { + let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`; + let requestOption = await this.requestOption(); + let response = await this.fetch200(url, requestOption); + this.passwords[path] = await response.text(); } + return this.passwords[path]; + } - /** - * 通过 id 获取 share drive 信息 - * @param any_id - * @returns {Promise} 任何非正常情况都返回 null - */ - async getShareDriveObjById(any_id) { - if (!any_id) return null; - if ('string' !== typeof any_id) return null; + /** + * 通过 id 获取 share drive 信息 + * @param any_id + * @returns {Promise} 任何非正常情况都返回 null + */ + async getShareDriveObjById(any_id) { + if (!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(); - let res = await fetch(url, requestOption); - let obj = await res.json(); - if (obj && obj.id) return obj; + let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`; + let requestOption = await this.requestOption(); + let res = await fetch(url, requestOption); + let obj = await res.json(); + if (obj && obj.id) return obj; - return null + return null; + } + + /** + * 搜索 + * @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>} + */ + async search(origin_keyword, page_token = null, page_index = 0) { + const types = CONSTS.gd_root_type; + const is_user_drive = this.root_type === types.user_drive; + const is_share_drive = this.root_type === types.share_drive; + + const empty_result = { + nextPageToken: null, + curPageIndex: page_index, + data: null, + }; + + if (!is_user_drive && !is_share_drive) { + return empty_result; + } + let keyword = FUNCS.formatSearchKeyword(origin_keyword); + if (!keyword) { + // 关键词为空,返回 + return empty_result; + } + let words = keyword.split(/\s+/); + let name_search_str = `name contains '${words.join( + "' AND name contains '" + )}'`; + + // corpora 为 user 是个人盘 ,为 drive 是团队盘。配合 driveId + let params = {}; + if (is_user_drive) { + params.corpora = "user"; + } + if (is_share_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; + params.supportsAllDrives = true; + } + if (page_token) { + 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.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); + // console.log(params) + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let res_obj = await response.json(); + + return { + nextPageToken: res_obj.nextPageToken || null, + curPageIndex: page_index, + data: res_obj, + }; + } + + /** + * 一层一层的向上获取这个文件或文件夹的上级文件夹的 file 对象。注意:会很慢!!! + * 最多向上寻找到当前 gd 对象的根目录 (root id) + * 只考虑一条单独的向上链。 + * 【注意】如果此id代表的项目不在目标gd盘下,那么此函数会返回null + * + * @param child_id + * @param contain_myself + * @returns {Promise<[]>} + */ + async findParentFilesRecursion(child_id, contain_myself = true) { + const gd = this; + const gd_root_id = gd.root.id; + const user_drive_real_root_id = authConfig.user_drive_real_root_id; + const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive; + + // 自下向上查询的终点目标id + const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id; + const fields = CONSTS.default_file_fields; + + // [{},{},...] + const parent_files = []; + let meet_top = false; + + async function addItsFirstParent(file_obj) { + if (!file_obj) return; + if (!file_obj.parents) return; + if (file_obj.parents.length < 1) return; + + // ['','',...] + let p_ids = file_obj.parents; + if (p_ids && p_ids.length > 0) { + // its first parent + const first_p_id = p_ids[0]; + if (first_p_id === target_top_id) { + meet_top = true; + return; + } + const p_file_obj = await gd.findItemById(first_p_id); + if (p_file_obj && p_file_obj.id) { + parent_files.push(p_file_obj); + await addItsFirstParent(p_file_obj); + } + } } + const child_obj = await gd.findItemById(child_id); + if (contain_myself) { + parent_files.push(child_obj); + } + await addItsFirstParent(child_obj); - /** - * 搜索 - * @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>} - */ - async search(origin_keyword, page_token = null, page_index = 0) { - const types = CONSTS.gd_root_type; - const is_user_drive = this.root_type === types.user_drive; - const is_share_drive = this.root_type === types.share_drive; + return meet_top ? parent_files : null; + } - const empty_result = { - nextPageToken: null, - curPageIndex: page_index, - data: null - }; - - if (!is_user_drive && !is_share_drive) { - return empty_result; - } - let keyword = FUNCS.formatSearchKeyword(origin_keyword); - if (!keyword) { - // 关键词为空,返回 - return empty_result; - } - let words = keyword.split(/\s+/); - let name_search_str = `name contains '${words.join("' AND name contains '")}'`; - - // corpora 为 user 是个人盘 ,为 drive 是团队盘。配合 driveId - let params = {}; - if (is_user_drive) { - params.corpora = 'user' - } - if (is_share_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; - params.supportsAllDrives = true; - } - if (page_token) { - 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.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); - // console.log(params) - let requestOption = await this.requestOption(); - let response = await fetch(url, requestOption); - let res_obj = await response.json(); - - return { - nextPageToken: res_obj.nextPageToken || null, - curPageIndex: page_index, - data: res_obj - }; + /** + * 获取相对于本盘根目录的path + * @param child_id + * @returns {Promise} 【注意】如果此id代表的项目不在目标gd盘下,那么此方法会返回空字符串"" + */ + async findPathById(child_id) { + if (this.id_path_cache[child_id]) { + return this.id_path_cache[child_id]; } + const p_files = await this.findParentFilesRecursion(child_id); + if (!p_files || p_files.length < 1) return ""; - /** - * 一层一层的向上获取这个文件或文件夹的上级文件夹的 file 对象。注意:会很慢!!! - * 最多向上寻找到当前 gd 对象的根目录 (root id) - * 只考虑一条单独的向上链。 - * 【注意】如果此id代表的项目不在目标gd盘下,那么此函数会返回null - * - * @param child_id - * @param contain_myself - * @returns {Promise<[]>} - */ - async findParentFilesRecursion(child_id, contain_myself = true) { - const gd = this; - const gd_root_id = gd.root.id; - const user_drive_real_root_id = authConfig.user_drive_real_root_id; - const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive; + 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 }); + }); - // 自下向上查询的终点目标id - const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id; - const fields = CONSTS.default_file_fields; + cache.forEach((obj) => { + this.id_path_cache[obj.id] = obj.path; + this.paths[obj.path] = obj.id; + }); - // [{},{},...] - const parent_files = []; - let meet_top = false; - - async function addItsFirstParent(file_obj) { - if (!file_obj) return; - if (!file_obj.parents) return; - if (file_obj.parents.length < 1) return; - - // ['','',...] - let p_ids = file_obj.parents; - if (p_ids && p_ids.length > 0) { - // its first parent - const first_p_id = p_ids[0]; - if (first_p_id === target_top_id) { - meet_top = true; - return; - } - const p_file_obj = await gd.findItemById(first_p_id); - if (p_file_obj && p_file_obj.id) { - parent_files.push(p_file_obj); - await addItsFirstParent(p_file_obj); - } - } - } - - const child_obj = await gd.findItemById(child_id); - if (contain_myself) { - parent_files.push(child_obj); - } - await addItsFirstParent(child_obj); - - return meet_top ? parent_files : null - } - - /** - * 获取相对于本盘根目录的path - * @param child_id - * @returns {Promise} 【注意】如果此id代表的项目不在目标gd盘下,那么此方法会返回空字符串"" - */ - async findPathById(child_id) { - if (this.id_path_cache[child_id]) { - return this.id_path_cache[child_id]; - } - - const p_files = await this.findParentFilesRecursion(child_id); - 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}) - }); - - cache.forEach((obj) => { - this.id_path_cache[obj.id] = obj.path; - this.paths[obj.path] = obj.id - }); - - /*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type; + /*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type; let path = '/' + p_files.map(it => it.name).reverse().join('/'); if (is_folder) path += '/';*/ - return cache[0].path; + 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 requestOption = await this.requestOption(); + let res = await fetch(url, requestOption); + return await res.json(); + } + + async findPathId(path) { + let c_path = "/"; + let c_id = this.paths[c_path]; + + let arr = path.trim("/").split("/"); + for (let name of arr) { + c_path += name + "/"; + + if (typeof this.paths[c_path] == "undefined") { + let id = await this._findDirId(c_id, name); + this.paths[c_path] = id; + } + + c_id = this.paths[c_path]; + if (c_id == undefined || c_id == null) { + break; + } + } + // console.log(this.paths); + return this.paths[path]; + } + + async _findDirId(parent, name) { + name = decodeURIComponent(name).replace(/\'/g, "\\'"); + + // console.log("_findDirId", parent, name); + + if (parent == undefined) { + return null; } - - // 根据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 requestOption = await this.requestOption(); - let res = await fetch(url, requestOption); - return await res.json() + 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); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let obj = await response.json(); + if (obj.files[0] == undefined) { + return null; } + return obj.files[0].id; + } - async findPathId(path) { - let c_path = '/'; - let c_id = this.paths[c_path]; - - let arr = path.trim('/').split('/'); - for (let name of arr) { - c_path += name + '/'; - - if (typeof this.paths[c_path] == 'undefined') { - let id = await this._findDirId(c_id, name); - this.paths[c_path] = id; - } - - c_id = this.paths[c_path]; - if (c_id == undefined || c_id == null) { - break; - } - } - // console.log(this.paths); - return this.paths[path]; + async accessToken() { + console.log("accessToken"); + 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; + this.authConfig.expires = Date.now() + 3500 * 1000; + } } + return this.authConfig.accessToken; + } - async _findDirId(parent, name) { - name = decodeURIComponent(name).replace(/\'/g, "\\'"); + async fetchAccessToken() { + console.log("fetchAccessToken"); + const url = "https://www.googleapis.com/oauth2/v4/token"; + const headers = { + "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", + }; - // console.log("_findDirId", parent, name); + let requestOption = { + method: "POST", + headers: headers, + body: this.enQuery(post_data), + }; - if (parent == undefined) { - return null; - } + const response = await fetch(url, requestOption); + return await response.json(); + } - 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); - let requestOption = await this.requestOption(); - let response = await fetch(url, requestOption); - let obj = await response.json(); - if (obj.files[0] == undefined) { - return null; - } - return obj.files[0].id; + async fetch200(url, requestOption) { + let response; + for (let i = 0; i < 3; i++) { + response = await fetch(url, requestOption); + console.log(response.status); + if (response.status != 403) { + break; + } + await this.sleep(800 * (i + 1)); } + return response; + } - async accessToken() { - console.log("accessToken"); - 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; - this.authConfig.expires = Date.now() + 3500 * 1000; - } - } - return this.authConfig.accessToken; + async requestOption(headers = {}, method = "GET") { + const accessToken = await this.accessToken(); + 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])); } + return ret.join("&"); + } - async fetchAccessToken() { - console.log("fetchAccessToken"); - const url = "https://www.googleapis.com/oauth2/v4/token"; - const headers = { - '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' - } - - let requestOption = { - 'method': 'POST', - 'headers': headers, - 'body': this.enQuery(post_data) - }; - - const response = await fetch(url, requestOption); - return await response.json(); - } - - async fetch200(url, requestOption) { - let response; - for (let i = 0; i < 3; i++) { - response = await fetch(url, requestOption); - console.log(response.status); - if (response.status != 403) { - break; - } - await this.sleep(800 * (i + 1)); - } - return response; - } - - async requestOption(headers = {}, method = 'GET') { - const accessToken = await this.accessToken(); - 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])); - } - return ret.join('&'); - } - - sleep(ms) { - return new Promise(function (resolve, reject) { - let i = 0; - setTimeout(function () { - console.log('sleep' + ms); - i++; - if (i >= 2) reject(new Error('i>=2')); - else resolve(i); - }, ms); - }) - } + sleep(ms) { + return new Promise(function(resolve, reject) { + let i = 0; + setTimeout(function() { + console.log("sleep" + ms); + i++; + 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(/^\s+|\s+$/g, ''); +String.prototype.trim = function(char) { + if (char) { + return this.replace( + new RegExp("^\\" + char + "+|\\" + char + "+$", "g"), + "" + ); + } + return this.replace(/^\s+|\s+$/g, ""); }; diff --git a/package-lock.json b/package-lock.json index 7222de0..8c682a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/public/index.html b/public/index.html index f3480ff..f3f1d89 100644 --- a/public/index.html +++ b/public/index.html @@ -14,7 +14,7 @@ <%= htmlWebpackPlugin.options.title %> <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> diff --git a/src/utils/AcrouUtil.js b/src/utils/AcrouUtil.js index 6f028cb..fa7d0b2 100644 --- a/src/utils/AcrouUtil.js +++ b/src/utils/AcrouUtil.js @@ -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,51 +168,63 @@ export function formatFileSize(bytes) { return bytes; } - /** 日期格式化 - * @param {Number String Date} + * @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) * @return {String} * @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; -} \ No newline at end of file +} diff --git a/src/views/common/BreadCrumb.vue b/src/views/common/BreadCrumb.vue index ab31710..81d4b56 100644 --- a/src/views/common/BreadCrumb.vue +++ b/src/views/common/BreadCrumb.vue @@ -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%; diff --git a/src/views/common/Navbar.vue b/src/views/common/Navbar.vue index 9570dd9..3cdcee5 100644 --- a/src/views/common/Navbar.vue +++ b/src/views/common/Navbar.vue @@ -42,7 +42,7 @@