Jelajahi Sumber

update 2024-05-16

junggilpark 1 tahun lalu
induk
melakukan
7dba08387b
5 mengubah file dengan 611 tambahan dan 215 penghapusan
  1. 47 0
      package-lock.json
  2. 1 0
      package.json
  3. 354 109
      src/app.js
  4. 18 0
      src/static/styles/custom.css
  5. 191 106
      src/views/hello.html

+ 47 - 0
package-lock.json

@@ -27,6 +27,7 @@
                 "jszip": "^3.10.1",
                 "multer": "^1.4.5-lts.1",
                 "path": "^0.12.7",
+                "pptxgenjs": "^3.12.0",
                 "restify": "^11.1.0",
                 "send": "^0.18.0",
                 "unzipper": "^0.11.4",
@@ -2205,6 +2206,11 @@
                 "node": ">=0.10"
             }
         },
+        "node_modules/https": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
+            "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
+        },
         "node_modules/https-proxy-agent": {
             "version": "7.0.4",
             "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
@@ -2274,6 +2280,20 @@
             "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
             "dev": true
         },
+        "node_modules/image-size": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
+            "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+            "dependencies": {
+                "queue": "6.0.2"
+            },
+            "bin": {
+                "image-size": "bin/image-size.js"
+            },
+            "engines": {
+                "node": ">=16.x"
+            }
+        },
         "node_modules/immediate": {
             "version": "3.0.6",
             "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -3237,6 +3257,25 @@
             "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
             "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="
         },
+        "node_modules/pptxgenjs": {
+            "version": "3.12.0",
+            "resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-3.12.0.tgz",
+            "integrity": "sha512-ZozkYKWb1MoPR4ucw3/aFYlHkVIJxo9czikEclcUVnS4Iw/M+r+TEwdlB3fyAWO9JY1USxJDt0Y0/r15IR/RUA==",
+            "dependencies": {
+                "@types/node": "^18.7.3",
+                "https": "^1.0.0",
+                "image-size": "^1.0.0",
+                "jszip": "^3.7.1"
+            }
+        },
+        "node_modules/pptxgenjs/node_modules/@types/node": {
+            "version": "18.19.33",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz",
+            "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==",
+            "dependencies": {
+                "undici-types": "~5.26.4"
+            }
+        },
         "node_modules/prelude-ls": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -3301,6 +3340,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/queue": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+            "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+            "dependencies": {
+                "inherits": "~2.0.3"
+            }
+        },
         "node_modules/queue-tick": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
         "jszip": "^3.10.1",
         "multer": "^1.4.5-lts.1",
         "path": "^0.12.7",
+        "pptxgenjs": "^3.12.0",
         "restify": "^11.1.0",
         "send": "^0.18.0",
         "unzipper": "^0.11.4",

+ 354 - 109
src/app.js

@@ -13,7 +13,8 @@ const JSZIP = require('jszip');
 require('dotenv').config({ path: './env/.env.test' });
 const session = require('express-session');
 const multer  = require('multer');
-const XLSX = require('xlsx'); 
+const XLSX = require('xlsx');
+const pptxgen = require('pptxgenjs');
 
 const {Document, Packer, Paragraph, TextRun} = require('docx');
 let WebSocketServer = require('websocket').server;
@@ -231,9 +232,9 @@ serverApp.post("/api-post", authProvider.acquireToken({
 }));
 
 serverApp.post("/getGroupList", authProvider.acquireToken({
-        scopes: ['.default'],
-        redirectUri: redirectUri,
-        successRedirect: '/group-redirect'
+    scopes: ['.default'],
+    redirectUri: redirectUri,
+    successRedirect: '/group-redirect'
 }));
   
 
@@ -244,10 +245,12 @@ serverApp.get("/group-redirect",
     try {
         const oneDrive = await getFetch(endPoint + "/me/drive/root", req.session.accessToken);
         const sharePointIds = await getFetch(endPoint + "/me/drive/SharePointIds", req.session.accessToken);
+        // const publicTeam = await getFetch(endPoint + "/groups?$filter=groupTypes/any(c:c+eq+'Unified')", req.session.accessToken);
         const graphResponse = await getFetch(endPoint + "/me/joinedTeams", req.session.accessToken);
         const sites = await getFetch(endPoint + "/sites/root", req.session.accessToken);
         const sitesSharePoint = await getFetch(endPoint + "/sites/root/SharePointIds", req.session.accessToken);
 
+        // const public = publicTeam.value;
         const teams = graphResponse.value;
         oneDrive.sharePoint = sharePointIds;
         sites.sharePoint = sitesSharePoint;
@@ -257,6 +260,7 @@ serverApp.get("/group-redirect",
           },
           joinedTeams : {
             teams : teams,
+            // teams : public,
             items : {},
           },
           sites : {
@@ -265,15 +269,17 @@ serverApp.get("/group-redirect",
         }
 
         if (teams && teams.length) {
-          const options = {
-            responseType: 'arraybuffer',
-            headers: {
-              Authorization: `Bearer ${req.session.accessToken}`,
-              ConsistencyLevel: 'eventual',
-              withCredentials:true,
-            },
-          };
+        // if (public && public.length) {
+          // const options = {
+          //   responseType: 'arraybuffer',
+          //   headers: {
+          //     Authorization: `Bearer ${req.session.accessToken}`,
+          //     ConsistencyLevel: 'eventual',
+          //     withCredentials:true,
+          //   },
+          // };
           for (let team of teams) {
+          // for (let team of public) {
             const item = await getFetch(endPoint + "/groups/"+team.id+"/drive/items/root/children", req.session.accessToken);
             const sharePoint = await getFetch(endPoint + "/groups/"+team.id+"/drive/SharePointIds", req.session.accessToken);
             // const image = await axios.get(endPoint + "/groups/" + team.id + "/photo/$value", options);
@@ -316,34 +322,65 @@ serverApp.post('/api/makeFolder',
           Authorization: `Bearer ${req.session.accessToken}`,
         },
       };
-      const {siteId, path, param} = req.body;
+      const {siteId, path, name} = req.body;
+      const resultObj = {message:"", success: 'F'};
+      const param ={
+        name: name,
+        folder: { },
+        '@microsoft.graph.conflictBehavior': 'rename'
+      }
       try{
         const sitesInfo = await axios.get(endPoint + "/sites/"+ siteId + path, options);
-        if (sitesInfo.data) {
+        if (sitesInfo.data && sitesInfo.data.id) {
           const itemId = sitesInfo.data.id;
-          const result = await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + itemId +"/children", JSON.parse(param), options);
-          res.json(result.data)
+          const result = await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + itemId +"/children",param, options);
+          
+          if (result.data) {
+            resultObj.message = "폴더가 생성되었습니다.<br>폴더명 : "+ name;
+            resultObj.success = 'S';
+          }
+          else {
+            resultObj.message = "폴더가 생성되지 않았습니다.";
+          }
+        }
+        else {
+          resultObj.message = "생성할 폴더 경로를 찾을 수 없습니다.";
         }
       }
       catch(error) {
-        console.log(error.response.data.error); 
-        
-        // resultObj.success = 'F';
-        // resultObj.message = '선택하신 파일 정보 삭제중 오류가 발생하였습니다.\n' + error.response.data.error.message;
-        // return res.json(resultObj);
+        resultObj.message = "폴더 생성 중 오류가 발생하였습니다.<br>" + getErrorMessage(error);
       }
-})
+
+      res.json(resultObj);
+});
+
+function getErrorMessage(error) {
+  let errorText = "";
+  if (error.response && error.response.status && error.response.statusText && error.response.data  && error.response.data.error) {
+    // console.log(error.response);
+    console.log('=============================== Axios Error ===============================')
+    console.log(error.response);
+    console.log(error.response.data.error);
+    errorText = `status : ${error.response.status} - ${error.response.statusText}<br>message : ${error.response.data.error.message}`;
+  }
+  else {
+    console.log('================================ Error =====================================')
+    console.log(error);
+    errorText = error;
+  }
+  return errorText;
+}
 
 serverApp.post('/api/makeWord', 
     isAuthenticated,
     isAccessTokens,
     async (req, res, next)=>{
-
-      const fileOptions = { headers: {
-        Authorization: `Bearer ${req.session.accessToken}`,
-        "Content-Type" : file.mimeType
-      }}
-      const {siteId, itemId, name} = req.body;
+      const options = {
+        headers: {
+          Authorization: `Bearer ${req.session.accessToken}`,
+        },
+      };
+      const {siteId, path, name} = req.body;
       const doc = new Document({
         sections : [
           {
@@ -356,17 +393,32 @@ serverApp.post('/api/makeWord',
             ]
           }
         ]
-      })
-      try{
-        await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+name+':/content', file.buffer, fileOptions);
-      }
-      catch(error) {
-        console.log(error.response.data.error); 
-        next(error);
-        // resultObj.success = 'F';
-        // resultObj.message = '선택하신 파일 정보 삭제중 오류가 발생하였습니다.\n' + error.response.data.error.message;
-        // return res.json(resultObj);
-      }
+      });
+
+      Packer.toBuffer(doc).then(async (buffer)=>{
+        const resultObj = {message:"", success: 'F'};;
+        try{
+          const sitesInfo = await axios.get(endPoint + "/sites/"+ siteId + path, options);
+          if (sitesInfo.data && sitesInfo.data.id) {
+            const itemId = sitesInfo.data.id;
+            const result = await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+name+':/content', buffer, options);
+            if (result.data) {
+              resultObj.message = "요청하신 Word 파일이 생성되었습니다.<br>파일명 : "+ name;
+              resultObj.success = 'S';
+            }
+            else {
+              resultObj.message = "요청하신 Word 파일이 생성되지 않았습니다.";
+            }
+          }
+          else {
+            resultObj.message = "생성할 폴더 경로를 찾을 수 없습니다.";
+          }
+        }
+        catch(error) {
+          resultObj.message = "요청하신 Word 파일 생성 중 오류가 발생하였습니다.<br>" + getErrorMessage(error);
+        }
+        res.send(resultObj);
+      });
 })
 
 serverApp.post('/api/makeExcel', 
@@ -376,7 +428,6 @@ serverApp.post('/api/makeExcel',
 
       const fileOptions = { headers: {
         Authorization: `Bearer ${req.session.accessToken}`,
-        "Content-Type" : 'application/ms-excel'
       }}
 
       const options = { headers: {
@@ -385,27 +436,58 @@ serverApp.post('/api/makeExcel',
       const {siteId, path, name} = req.body;
       const wb = XLSX.utils.book_new();
       XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet([]), 'Sheet1');
-      XLSX.writeFile(wb, name);
-      const data = fs.readFileSync(name);
+      var wbout = XLSX.write(wb, {bookType:'xlsx',  type: 'buffer'});
+      const resultObj = {message:'', success: 'F'};
       try{
         const parentData = await axios.get(endPoint + "/sites/"+ siteId +path, options);
         if (parentData.data) {
-          const result = await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+parentData.data.id+':/'+name+':/content', data, fileOptions);
-          fs.unlinkSync(name);
+          const result = await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+parentData.data.id+':/'+name+':/content', wbout, options);
           if (result.data) {
-            return res.json({message:'요청하신 Excel 파일이 생성 되었습니다.', success: 'S'});
+            resultObj.message ='요청하신 Excel 파일이 생성 되었습니다.';
+            resultObj.success = 'S';
+          }
+          else{
+            resultObj.message = '요청하신 Excel 파일이 생성 되지 않았습니다.';
           }
-          return res.json({message:'요청하신 Excel 파일이 생성 되지 않았습니다.', success: 'F'});
         }
       }
       catch(error) {
-        console.log(error.response.data.error); 
-        return res.json({message:'요청하신 Excel 파일 생성 중 오류가 발생하였습니다.', success: 'F', error: error});
-        // next(error);
-        // resultObj.success = 'F';
-        // resultObj.message = '선택하신 파일 정보 삭제중 오류가 발생하였습니다.\n' + error.response.data.error.message;
-        // return res.json(resultObj);
+        resultObj.message = "요청하신 Word 파일 생성 중 오류가 발생하였습니다.<br>" + getErrorMessage(error);
       }
+      res.json(resultObj);
+})
+
+serverApp.post('/api/makePptx', 
+    isAuthenticated,
+    isAccessTokens,
+    async (req, res, next)=>{
+
+      const options = { headers: {
+        Authorization: `Bearer ${req.session.accessToken}`,
+      }}
+      const {siteId, path, name} = req.body;
+      const pres = new pptxgen();
+      pres.addSlide("TITLE_SLIDE");
+      const resultObj = {message:'', success: 'F'};
+      pres.stream().then(async (data)=>{
+        try{
+          const parentData = await axios.get(endPoint + "/sites/"+ siteId +path, options);
+          if (parentData.data) {
+            const result = await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+parentData.data.id+':/'+name+':/content', data, options);
+            if (result.data) {
+              resultObj.message ='요청하신 PowerPoint 프레젠테이션 파일이 생성 되었습니다.';
+              resultObj.success = 'S';
+            }
+            else{
+              resultObj.message = '요청하신 PowerPoint 프레젠테이션 파일이 생성 되지 않았습니다.';
+            }
+          }
+        }
+        catch(error) {
+          resultObj.message = "요청하신 PowerPoint 프레젠테이션 파일 생성 중 오류가 발생하였습니다.<br>" + getErrorMessage(error);
+        }
+        res.json(resultObj);
+      })
 })
 
 serverApp.post('/api/check-name', 
@@ -460,19 +542,141 @@ serverApp.post('/api/check-name',
       }
 })
 
+// serverApp.post('/api/upload', upload.array('file'),
+//   isAuthenticated,
+//   isAccessTokens,
+//   async (req, res, next)=>{
+//     const startTime = new Date();
+//     const folderParam = {
+//       name: '',
+//       folder: { },
+//       '@microsoft.graph.conflictBehavior': 'rename'
+//     }
+//     const files = req.files;
+//     let {siteId, path, folder} = req.body;
+
+//     if (siteId && path) {
+//       const options = {
+//         headers: {
+//           Authorization: `Bearer ${req.session.accessToken}`,
+//         },
+//       };
+
+//         if (folder) {
+//           if (!Array.isArray(folder)) {
+//             folder = [folder];
+//           }
+//           let beforeUri = '';
+//           let beforeItemId = '';
+//           for (let item of folder) {
+//             const fileInfo = JSON.parse(item);
+//             const param = {...folderParam};
+//             param.name = fileInfo.name;
+//             let folderPath = '';
+//             if (fileInfo.path) {
+//               folderPath = fileInfo.path; 
+//               if (!path.includes(':')) {
+//                 folderPath = ":" + folderPath;
+//               }
+//             }
+//             let uri = endPoint + "/sites/"+ siteId + path + folderPath;
+//             try {
+              
+//               let itemId = '';
+//               if (beforeUri === uri) {
+//                 itemId = beforeItemId;
+//               }
+//               else {
+//                 const sitesInfo = await axios.get(uri, options);
+//                 itemId = sitesInfo.data.id;
+//                 beforeItemId = itemId;
+//                 beforeUri = uri;
+//               }
+//               await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + itemId +"/children", param, options);
+//             }
+//             catch(error) {
+//               return res.json({success:'F', message: '요청하신 파일 업로드 중 오류가 발생하였습니다.<br>' + getErrorMessage(error)});
+//             }
+//           }
+//         }
+
+//         if (files && files.length > 0) {
+//           const promiseArray = [];
+//           let beforeUri = '';
+//           let beforeItemId = '';
+//           for (let file of files) {
+//             // const fileName = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
+//             const fileName = file.originalname;
+//             let filePath = req.body[ fileName + "_path"];
+//             file.originalname = Buffer.from(file.originalname, 'ascii').toString('utf8');
+
+//             let formatPath = '';
+//             if (filePath) {
+//               if (Array.isArray(filePath) && filePath.length > 0) {
+//                 formatPath = filePath[0];
+//                 if (filePath.length > 1) {
+//                   req.body[ fileName + "_path"] = filePath.splice(1); 
+//                 }
+//               }
+//               else if (filePath.trim()){
+//                 formatPath = filePath;
+//                 }
+                
+//                 if (!path.includes(":")) {
+//                   formatPath = ":" + formatPath;
+//                 }
+//               }
+//               let itemId = '';
+//               const uri = endPoint + "/sites/"+ siteId + path + formatPath;
+              
+//               if (beforeUri === uri) {
+//                 itemId = beforeItemId;
+//               }
+//               else {
+//                 const sitesInfo = await axios.get(uri, options);
+//                 itemId = sitesInfo.data.id;
+//                 beforeUri = uri;
+//                 beforeItemId = itemId; 
+//               }
+//               const fileOptions = { headers: {
+//                 Authorization: `Bearer ${req.session.accessToken}`,
+//                 "Content-Type" : file.mimeType
+//               }}
+//               // await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content', file.buffer, fileOptions);
+//               promiseArray.push(axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content', file.buffer, fileOptions));
+//           }
+
+//           if (promiseArray.length > 0) {
+//             try {
+//               const result = await Promise.all(promiseArray);
+//               console.log(result.length);
+
+//             }
+//             catch(error){
+//               return res.json({success:'F', message: '요청하신 파일 업로드 중 오류가 발생하였습니다.<br>' + getErrorMessage(error)});
+//             }
+//           }
+//         }
+//         const endTime = new Date();
+//         let betweenTime = endTime - startTime;
+//         if (betweenTime > 60000) {
+//           betweenTime = (betweenTime/1000/60) + ' 분';
+//         }
+//         else {
+//           betweenTime = (betweenTime/1000) + ' 초';       
+//         }
+//         console.log('시작 시간 :', startTime.toLocaleString(), ', 종료 시간 :', endTime.toLocaleString(), ', 소요 시간 :', betweenTime);
+//         res.json({success:'S', message: '요청하신 파일 업로드가 정상적으로 처리 되었습니다.'});
+//       }
+// });
+
 serverApp.post('/api/upload', upload.array('file'),
   isAuthenticated,
   isAccessTokens,
   async (req, res, next)=>{
     const startTime = new Date();
-    const folderParam = {
-      name: '',
-      folder: { },
-      '@microsoft.graph.conflictBehavior': 'rename'
-    }
     const files = req.files;
     let {siteId, path, folder} = req.body;
-
     if (siteId && path) {
       const options = {
         headers: {
@@ -488,8 +692,13 @@ serverApp.post('/api/upload', upload.array('file'),
           let beforeItemId = '';
           for (let item of folder) {
             const fileInfo = JSON.parse(item);
-            const param = {...folderParam};
-            param.name = fileInfo.name;
+
+            const param = {
+              name: fileInfo.name,
+              folder: {},
+              '@microsoft.graph.conflictBehavior': 'rename'
+            };
+
             let folderPath = '';
             if (fileInfo.path) {
               folderPath = fileInfo.path; 
@@ -500,55 +709,88 @@ serverApp.post('/api/upload', upload.array('file'),
             let uri = endPoint + "/sites/"+ siteId + path + folderPath;
             try {
               
-              let itemId = '';
-              if (beforeUri === uri) {
-                itemId = beforeItemId;
-              }
-              else {
-                const sitesInfo = await axios.get(uri, options);
-                itemId = sitesInfo.data.id;
-                beforeItemId = itemId;
-                beforeUri = uri;
-              }
-              await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + itemId +"/children", param, options);
-              
-              // await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + itemId +"/children", param, options);
+              // let itemId = '';
+              // if (beforeUri === uri) {
+              //   itemId = beforeItemId;
+              // }
+              // else {
+              //   const sitesInfo = await axios.get(uri, options);
+              //   itemId = sitesInfo.data.id;
+              //   beforeItemId = itemId;
+              //   beforeUri = uri;
+              // }
+              const result = await new Promise (async (resolve, reject)=>{
+                try {
+                  const sitesInfo = await axios.get(uri, options);
+                  resolve(sitesInfo.data);
+                }
+                catch(error) {
+                  if (error.response) {
+                    reject(error.response);
+                  }
+                  else {
+                    reject(error);
+                  }
+                }
+              }).then(async (result)=>{
+                return await new Promise(async (resolve, reject)=>{
+                  try {
+                    const uploadFolder = await axios.post(endPoint + "/sites/"+ siteId +"/drive/items/" + result.id +"/children", param, options);
+                    resolve(uploadFolder.data);
+                  }
+                  catch(error){
+                    if (error.response) {
+                      reject(error.response);
+                    }
+                    else {
+                      reject(error);
+                    }
+                  }
+                })
+              }).catch((error)=>{
+                console.log(error);
+              })
+              console.log(result.createdDateTime);
+              // console.log(result);
             }
             catch(error) {
-              if (error.response) {
-                console.log(error.response.data);
-              } 
-              else {
-                console.log(error);
-              }
-                // next(error);
-              return res.json({success:'F', message: '요청하신 파일 업로드 중 오류가 발생하였습니다.', error : error});
+              return res.json({success:'F', message: '요청하신 파일 업로드 중 오류가 발생하였습니다.<br>' + getErrorMessage(error)});
             }
           }
+          const makeFolderTime = new Date();
+
+          let betweenTime = makeFolderTime - startTime;
+          if (betweenTime > 60000) {
+            betweenTime = (betweenTime/1000/60) + ' 분';
+          }
+          else {
+            betweenTime = (betweenTime/1000) + ' 초';       
+          }
+          console.log('폴더 시작 시간 :', startTime.toLocaleString(), ', 폴더 종료 시간 :', makeFolderTime.toLocaleString(), ', 소요 시간 :', betweenTime);
+
         }
+
+        const promiseArray = [];
         if (files && files.length > 0) {
-          const promiseArray = [];
           let beforeUri = '';
           let beforeItemId = '';
           for (let file of files) {
-            // const fileName = file.originalname.substring(0, file.originalname.lastIndexOf('.'));
             const fileName = file.originalname;
             let filePath = req.body[ fileName + "_path"];
             file.originalname = Buffer.from(file.originalname, 'ascii').toString('utf8');
 
-            try { 
-              let formatPath = '';
-              if (filePath) {
-                if (Array.isArray(filePath) && filePath.length > 0) {
-                  formatPath = filePath[0];
-                  if (filePath.length > 1) {
-                    req.body[ fileName + "_path"] = filePath.splice(1); 
-                  }
+            let formatPath = '';
+            if (filePath) {
+              if (Array.isArray(filePath) && filePath.length > 0) {
+                formatPath = filePath[0];
+                if (filePath.length > 1) {
+                  req.body[ fileName + "_path"] = filePath.splice(1); 
                 }
-                else if (filePath.trim()){
-                  formatPath = filePath;
+              }
+              else if (filePath.trim()){
+                formatPath = filePath;
                 }
-
+                
                 if (!path.includes(":")) {
                   formatPath = ":" + formatPath;
                 }
@@ -565,28 +807,31 @@ serverApp.post('/api/upload', upload.array('file'),
                 beforeUri = uri;
                 beforeItemId = itemId; 
               }
-              // const uploadUri = endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content';
               const fileOptions = { headers: {
                 Authorization: `Bearer ${req.session.accessToken}`,
                 "Content-Type" : file.mimeType
               }}
-              await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content', file.buffer, fileOptions);
-            }
-            catch(error) {
-              if (error) {
-                if (error.response) {
-                  console.log(error.response.data);
-                } 
-                else {
-                  console.log(error);
-                }
-              }
-              return res.json({success:'F', message: '요청하신파일 업로드 중 오류가 발생하였습니다.', error : error});
-            }
+              // await axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content', file.buffer, fileOptions);
+              promiseArray.push(axios.put(endPoint + "/sites/"+ siteId +"/drive/items/"+itemId+':/'+file.originalname+':/content', file.buffer, fileOptions));
+          }
+        }
+        if (promiseArray.length > 0) {
+          try {
+            const result = await Promise.all(promiseArray);
+          }
+          catch(error){
+            return res.json({success:'F', message: '요청하신 파일 업로드 중 오류가 발생하였습니다.<br>' + getErrorMessage(error)});
           }
         }
         const endTime = new Date();
-        console.log('시작 시간 :', startTime, '종료 시간 :', endTime);
+        let betweenTime = endTime - startTime;
+        if (betweenTime > 60000) {
+          betweenTime = (betweenTime/1000/60) + ' 분';
+        }
+        else {
+          betweenTime = (betweenTime/1000) + ' 초';       
+        }
+        console.log('시작 시간 :', startTime.toLocaleString(), ', 종료 시간 :', endTime.toLocaleString(), ', 소요 시간 :', betweenTime);
         res.json({success:'S', message: '요청하신 파일 업로드가 정상적으로 처리 되었습니다.'});
       }
 });

+ 18 - 0
src/static/styles/custom.css

@@ -332,6 +332,10 @@ li {
   min-height: 20px;
   padding: 16px 46px 20px 24px;
   line-height: normal;
+  user-select: none;
+}
+.modal.red .modal-box .title {
+  color: red;
 }
 .modal .modal-box .modal-content {
   padding: 0 30px;
@@ -340,6 +344,10 @@ li {
   display: flex;
   flex-direction: column;
   justify-content: center;
+  overflow: auto;
+}
+.modal.red .modal-box .modal-content {
+  color:red;
 }
 .modal .modal-box .modal-content input {
   width: 100%;
@@ -351,6 +359,10 @@ li {
   
 }
 
+.modal.red .modal-box .modal-content input:focus{
+  border-color: red;
+  outline-color: red;
+}
 .modal .modal-box .modal-content input:focus{
   border-color: #5b5fc7;
   outline-color: #5b5fc7;
@@ -366,6 +378,10 @@ li {
   height: 40px;
   margin-bottom: 10px;
 }
+.modal.red .modal-box .button-box > div {
+  border: 1px solid red;
+  background-color: red;
+}
 .modal .modal-box .button-box > div {
   padding: 5px 10px;
   border-radius: 5px;
@@ -373,6 +389,7 @@ li {
   background-color: #5b5fc7;
   color: white;
   font-weight: bold;
+  user-select: none;
 }
 
 .modal .modal-box .button-box > div:hover{
@@ -388,6 +405,7 @@ li {
   right: 0px;
   padding: 15px 15px 0px 0px;
   cursor: pointer;
+  user-select: none;
 }
 .sp-name:hover {
   text-decoration: underline;

+ 191 - 106
src/views/hello.html

@@ -41,7 +41,7 @@
                         <div onclick="mkdir()"><img src="/static/images/folder.png" width="20" height="20" alt="문서 이미지">&nbsp;폴더</div>
                         <div onclick="mkWord()"><img src="/static/images/docx.svg" width="20" height="20" alt="문서 이미지">&nbsp;Word 문서</div>
                         <div onclick="mkExcel()"><img src="/static/images/xlsx.svg" width="20" height="20" alt="문서 이미지">&nbsp;Excel 통합 문서</div>
-                        <div><img src="/static/images/pptx.svg" width="20" height="20" alt="문서 이미지">&nbsp;PowerPoint 프레젠테이션</div>
+                        <div onclick="mkPpt()"><img src="/static/images/pptx.svg" width="20" height="20" alt="문서 이미지">&nbsp;PowerPoint 프레젠테이션</div>
                       </div> 
                     </div>
                     <div onclick="menuOpen(event, 'upload-items')">
@@ -108,13 +108,13 @@
 
   function openFolder(event) {
     $('.upload-items').css('display', 'none');
-    if (!$('.panel-item.on')[0]) return alertMessage('폴더 업로드', '업로드할 그룹을 선택해 주세요.');
+    if (!$('.panel-item.on')[0]) return alertMessage('폴더 업로드', '업로드할 그룹을 선택해 주세요.', null, 'red');
     $('#folder-upload').click();
   }
 
   function openFile(event) {
     $('.upload-items').css('display', 'none');
-    if (!$('.panel-item.on')[0]) return alertMessage('파일 업로드', '업로드할 그룹을 선택해 주세요.');
+    if (!$('.panel-item.on')[0]) return alertMessage('파일 업로드', '업로드할 그룹을 선택해 주세요.', null, 'red');
     $('#file-upload').click();
   }
   function allSelect(event) {
@@ -132,53 +132,86 @@
     if (files.length === 0) return;
     const alertTitle = '파일 업로드';
     const formData = new FormData($('<form enctype="multipart/form-data"></form>')[0]);
+    const driveFileNames = getDriveFileNames();
     for (let file of files) {
       let fileName = file.name;
+      if (driveFileNames.length > 0 && driveFileNames.includes(fileName)) {
+        $(event.target).val("");
+        return alertMessage(alertTitle, '업로드 경로에 이미 같은 이름의 파일이 있습니다.<br>파일을 업로드 하려면 기존 파일의 명칭을 변경해주세요.<br>파일명 : '+ fileName, null, 'red');
+      }
       formData.append('file', file);
     }
 
     upload(formData);
   }
 
+  function getDriveFileNames() {
+    const driveFiles = $('section .file-content div .sp-name');
+    const result = [];
+    if (driveFiles.length) {
+      for (let ii = 0; ii < driveFiles.length; ii++) {
+          const name = driveFiles.eq(ii).text();
+          if (name && name.trim()) {
+            result.push(name);
+          }
+      }
+    }
+    return result;
+  }
+
   function uploadFolders(event) {
     const srcEl = event.srcElement;
+    const alertTitle = '폴더 업로드';
     if (srcEl && srcEl.files) {
       const files = srcEl.files;
       if (files.length > 100) {
-        return alertMessage('폴더 업로드', '업로드 파일은 최대 100개 까지 가능합니다.<br>업로드 파일 수 : '+ files.length);
+        return alertMessage(alertTitle, '업로드 파일 수 : '+ files.length + '<br>업로드 파일은 최대 100개 까지 가능합니다.', null, 'red');
       }
       let folders = {};
       const siteId = getSitesId();
       let formData = new FormData($('<form enctype="multipart/form-data"></form>')[0]);
+      const driveFileNames = getDriveFileNames();
+      if (files.length) {
+        const filePath = files[0].webkitRelativePath;
+        const rootFolder = filePath.substring(0, filePath.indexOf('/'));
+        console.log(rootFolder);
+        if (driveFileNames.includes(rootFolder)) {
+          $(srcEl).val("");
+          return alertMessage(alertTitle, '폴더명 : '+ rootFolder + '<br>업로드 경로에 이미 같은 이름의 폴더가 있습니다.<br>폴더를 업로드 하려면 기존 폴더의 명칭을 변경해주세요.', null, 'red');
+        }
+      }
       for (let file of files) {
         let path = file.webkitRelativePath;
-        path = path.substring(0, path.lastIndexOf('/'));
+        path =  '/'+ path.substring(0, path.lastIndexOf('/'));
         formData.append('file', file);
         let filePath = path;
-        if (filePath.substring(0,1) !== '/' ) {
-          filePath = '/'+filePath;
-        }
+
         formData.append(file.name + '_path', filePath);
-        if (!folders[path]) {
-          let name = path
-          if (name.lastIndexOf('/') > -1) {
-            name = name.substring(0, name.lastIndexOf('/'));
-          }
-          let folderPath = name;
-          if (folderPath === path) {
-            folderPath = "";
+
+        if (!folders[path] && path.indexOf('/') >= 0) {
+          let folderPath = "";
+          if (path.lastIndexOf('/') > 0) {
+            folderPath = path.substring(0, path.lastIndexOf('/'));
           }
+
+          let name = path.substring(path.lastIndexOf('/') + 1, path.length);
           formData.append('folder', JSON.stringify({name : name, path : folderPath}));
           folders[path] = true;
         }
       }
+
       upload(formData);
+
+      $(srcEl).val("");
     }
   }
 
   function menuOpen(event, className) {
     event.preventDefault();
     event.stopPropagation();
+    if ($('.panel-item').length === 0) {
+      return;
+    }
     const toggleBox = document.getElementsByClassName('toggle-box');
     if (document.getElementsByClassName('toggle-box')){
       $(toggleBox).remove();
@@ -358,10 +391,7 @@
     }
 
     function openWebUrl(url, name, event) {
-      // console.log(url, name, event);
       window.open(url);
-      // let iframe = `<iframe allowfullscreen="true" src="${url}" style="position:absolute; width:100%; height:100%"></iframe>`;
-      // $('body').append(iframe);
     }
 
     function addTabs() {
@@ -613,7 +643,7 @@
         for (let ii = 0; ii < moveArr.length; ii++) {
           for (let jj = 0; jj < fileArr.length; jj++) {
               if (moveArr.eq(ii).text() === fileArr.eq(jj).text()) {
-                return alertMessage(alertTitle, '대상 경로에 이미 같은 이름의 파일이 있습니다.<br>파일을 '+text+' 하려면 해당 파일의 명칭을 변경해주세요.<br>파일명 : ' + moveArr.eq(ii).text());
+                return alertMessage(alertTitle, '파일명 : ' + moveArr.eq(ii).text() +'<br>대상 경로에 이미 같은 이름의 파일이 있습니다.<br>파일을 '+text+' 하려면 해당 파일의 명칭을 변경해주세요.');
               }
           }
         }
@@ -1104,9 +1134,7 @@
                 },
                 success: (res)=> {
                   if (res.success === 'S') {
-                      const selectedDrive = $('.panel-item.on');
-                      selectedDrive.removeClass('on');
-                      selectedDrive.click();
+                    refreshDrives();
                   }
                   $('.modal').remove();
                   alertMessage('파일 삭제', res.message);
@@ -1257,7 +1285,6 @@
           const items = transfer.items;
           _formData = new FormData($('<form enctype="multipart/form-data"></form>')[0]);
           const result = await getFilesDataTransferItems(items);
-          console.log(result);
           upload(_formData);
       });
     }
@@ -1269,22 +1296,22 @@
         if (folders.length === 0 && files.length === 0) return;
         
         if (folders && folders.length > 100) {
-          return alertMessage(alertTitle, '업로드 폴더는 최대 100개 까지 가능합니다.<br>업로드 폴더 수 : '+ folders.length, null);
+          return alertMessage(alertTitle, '업로드 폴더는 최대 100개 까지 가능합니다.<br>업로드 폴더 수 : '+ folders.length, null, 'red');
         }
 
         if (files && files.length > 100) {
-          return alertMessage(alertTitle, '업로드 파일은 최대 100개 까지 가능합니다.\n업로드 파일 수 : '+ files.length, null);
+          return alertMessage(alertTitle, '업로드 파일은 최대 100개 까지 가능합니다.\n업로드 파일 수 : '+ files.length, null, 'red');
         }
         let siteId = getSitesId();
         if (siteId === null) {
-          return alertMessage(alertTitle, '선택된 그룹 정보를 찾을 수 없습니다. 생성할 그룹을 선택해주세요.', null);
+          return alertMessage(alertTitle, '선택된 그룹 정보를 찾을 수 없습니다. 생성할 그룹을 선택해주세요.', null, 'red');
         }
         
         const groupIndex = _listData.joinedTeams.teams.findIndex(obj => obj.sharePoint.siteId === siteId);
         const pathArr = $('.panel').children();
 
         if (groupIndex >= 0 && $('.panel').children().length === 1) {
-          return alertMessage(alertTitle,'채널에는 업로드 할 수 없습니다. 채널 리스트를 먼저 선택해 주세요.');
+          return alertMessage(alertTitle,'채널에는 업로드 할 수 없습니다. 채널 리스트를 먼저 선택해 주세요.', null, 'red');
         }
 
         let sitePath = getDrivePath();
@@ -1300,24 +1327,29 @@
           url : '/api/upload',
           success: (res) => {
             let str = res.message;
-            if (res.error) {
-              str += '<br>오류 : ' + res.error.message;
+            let color = null;
+            if (res.success === 'F') {
+              // str += '<br>오류 : ' + res.error.message;
+              color = 'red';
             }
-            alertMessage(alertTitle, str, null);
-            const selectDrive = $('.panel-item.on');
-            selectDrive.removeClass('on');
-            selectDrive.click();
+            alertMessage(alertTitle, str, null, color);
+            refreshDrives();
             
           },
           error: (error) => {
             console.log(error);
-            alertMessage(alertTitle, error, null);
+            alertMessage(alertTitle, error, null, 'red');
           },
           
         });
     }
 
-    function alertMessage(title, message, color) {
+    function alertMessage(title, message, id, color) {
+      let method = "modalClose('alert')";
+      if (id) {
+        method = `modalClose('alert', '${id}')`;
+      }
+      
       $('body').append($(`<div class="modal alert" style="display:flex;">
           <div class="modal-box">
             <div class="header">
@@ -1330,11 +1362,14 @@
                   ${message}
                 </div>
                 <div class="button-box">
-                    <div class="name-btn" onclick="modalClose('alert')">확인</div>
+                    <div class="name-btn" onclick="${method}">확인</div>
                 </div>
             </div>
           </div>
         </div>`));
+        if (color) {
+          $('.modal.alert').addClass(color);
+        }
     }
 
     function confirmMessage(title, message, method) {
@@ -1468,12 +1503,14 @@
               }
               _formData.append('file', file);
               _formData.append(fileName + '_path', path);
+              console.log(path);
               resolve(fileObj);
             });
           } else if (item.isDirectory) {
             let dirReader = item.createReader();
             const path = item.fullPath.substring(0, item.fullPath.lastIndexOf('/'));
             _formData.append('folder', JSON.stringify({name : item.name, path : path}));
+            console.log(path);
             dirReader.readEntries(entries => {
               let entriesPromises = [];
               fileObj = {
@@ -1761,53 +1798,57 @@
     window.URL.revokeObjectURL(downloadUrl);
   }
 
-  function makeFolder() {
-    const $folderName = $('#folder_name');
-    const name = $folderName.val();
-    if (!name || !name.trim()) {
-      $folderName.focus();
-      return alertMessage("폴더 생성", "폴더명을 입력해주세요.");
+  //새로만들기 이벤트
+  function makeItems(id, name, url, ext) {
+    const $name = $('#'+id);
+    let nameVal = $name.val();
+    if (!nameVal || !nameVal.trim()) {
+      return alertMessage(name, name + "명을 입력해주세요.", id, 'red');
+    }
+    const pattern = /[<>\:\*\"\/\\\?\|]/gi;
+    if (pattern.test(nameVal)) {
+      $name.focus();
+      return alertMessage(name, name + `명에 해당 특수 문자를 포함 할 수 없습니다.( \\ / : * ? " < > | )`, id, 'red');
+    }
+    if (ext) {
+      nameVal += ext;
     }
-    let siteId = getSitesId();
+
+    const siteId = getSitesId();
     if (siteId === null) {
-      return alertMessage("폴더 생성", '선택된 그룹 정보를 찾을 수 없습니다. 생성할 그룹을 선택해주세요.');
+      return alertMessage(name, '선택된 그룹 정보를 찾을 수 없습니다. 생성할 그룹을 선택해주세요.', null, 'red');
     }
-    
     const groupIndex = _listData.joinedTeams.teams.findIndex(obj => obj.sharePoint.siteId === siteId);
     const pathArr = $('.panel').children();
 
     if (groupIndex >= 0 && $('.panel').children().length === 1) {
-      return alertMessage("폴더 생성", '채널 리스트를 먼저 선택해 주세요.');
+      return alertMessage(name, '채널 리스트를 먼저 선택해 주세요.', null, 'red');
     }
-    let sitePath = '/drive/root'
-    if (pathArr.length > 1) {
-      sitePath += ':';
-      for (let ii = 0; ii < pathArr.length; ii++) {
-        const path = pathArr.eq(ii).text();
-        if (ii !== 0 && path !== " > ") {
-          sitePath += "/" + path; 
-        }
+
+    const fileArr = $('section .file-content .sp-name');
+    for (let ii = 0; ii < fileArr.length; ii++) {
+      if (fileArr.eq(ii).text() === nameVal) {
+        $name.focus();
+        return alertMessage(name, '파일명 : ' + nameVal +'<br>이미 같은 이름의 파일이 있습니다.<br>생성할 파일 명칭을 변경해주세요.', id, 'red');
       }
     }
 
+    const path = getDrivePath();
+
     $.ajax({
       method: 'post',
+      url : url,
       data : {
         siteId : siteId,
-        path : sitePath,
-        param : JSON.stringify({
-          name: name,
-          folder: { },
-          '@microsoft.graph.conflictBehavior': 'rename'
-        })
+        path : path,
+        name : nameVal,
       },
-      url : '/api/makeFolder',
       success: (res) => {
-        $('.modal').remove();
-        alertMessage("폴더 생성", '폴더가 생성되었습니다.<br>폴더명 : '+ res.name);
-        const selectDrive = $('.panel-item.on');
-        selectDrive.removeClass('on');
-        selectDrive.click();
+        alertMessage(name, res.message);
+        if (res.success === 'S') {
+          modalClose('make');
+          refreshDrives();
+        }
       },
       error: (error) => {
         console.log('==============error=============\n');
@@ -1815,84 +1856,124 @@
       }
       
     })
-  }
 
+  }
 
   //새폴더 생성
   function mkdir() {
     const modalContainer = $(
-      `<div class="modal folder" style="display: flex;">
+      `<div class="modal make" style="display: flex;">
           <div class="modal-box">
               <div class="header">
                 <div class="title">폴더 생성</div>
                 <div class="x-button">
-                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('folder')"></span>
+                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('make')"></span>
                 </div>
               </div>
               <div class="modal-content">
                 <div>폴더명</div>
-                <input type="text" name="file_name" id="folder_name" value="">
+                <input type="text" name="file_name" autocomplete='off' onkeyup="enterKey(()=>makeItems('folder_name', '폴더', '/api/makeFolder', null), event)" id="folder_name" value="">
               </div>
               <div class="button-box">
-                  <div class="name-btn" onclick="makeFolder()">만들기</div>
-                  <div class="name-btn" onclick="modalClose('folder')">취소</div>
+                  <div class="name-btn" onclick="makeItems('folder_name', '폴더', '/api/makeFolder', null)">만들기</div>
+                  <div class="name-btn" onclick="modalClose('make')">취소</div>
               </div>
           </div>
       </div>`);
-      $('body').append(modalContainer);
-    
+    $('body').append(modalContainer);
+    $('#folder_name').focus();
   }
+
+  //새 엑셀 파일 생성
   function mkExcel() {
-    const siteId = getSitesId();
     const modalContainer = $(
-      `<div class="modal excel" style="display: flex;">
+      `<div class="modal make" style="display: flex;">
           <div class="modal-box">
               <div class="header">
                 <div class="title">Excel 통합 문서</div>
                 <div class="x-button">
-                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('excel')"></span>
+                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('make')"></span>
                 </div>
               </div>
               <div class="modal-content">
                 <div>이름</div>
                 <div style="display:flex; align-items:flex-end; user-select:none;">
-                  <input type="text" placeholder="새 이름을 입력하세요." style="width:'calc(100% - 35px)';" name="change_name" id="excel_input" value="">&nbsp;.xlsx
+                  <input type="text" placeholder="새 이름을 입력하세요." style="width:'calc(100% - 35px)';" autocomplete='off' onkeyup="enterKey(()=>makeItems('excel_input', 'Excel 통합 문서', '/api/makeExcel', '.xlsx'), event)" name="change_name" id="excel_input" value="">&nbsp;.xlsx
                 </div>
               </div>
               <div class="button-box">
-                  <div class="name-btn" onclick="makeExcel('${siteId}')">만들기</div>
-                  <div class="cancel-btn" onclick="modalClose('excel')">취소</div>
+                  <div class="name-btn" onclick="makeItems('excel_input', 'Excel 통합 문서', '/api/makeExcel', '.xlsx')">만들기</div>
+                  <div class="cancel-btn" onclick="modalClose('make')">취소</div>
               </div>
           </div>
       </div>`);
       $('body').append(modalContainer);
+      $('#excel_input').focus();
   }
-  function makeExcel(siteId) {
-    const name = $('#excel_input').val();
-    if (!name && !name.trim()) {
-      return alertMessage('Excel 통합 문서', '생성할 Excel 이름을 입력해주세요.');
-    }
 
-    const path = getDrivePath();
-    $.ajax({
-      url: '/api/makeExcel',
-      method : 'post',
-      data : {
-        siteId : siteId,
-        name : name + '.xlsx',
-        path : path,
-      },
-      success: (res)=> {
-        console.log(res)
-      },
-      error: (error)=>{
-        console.log(error);
-      }
-    })
+  function mkWord() {
+    const modalContainer = $(
+      `<div class="modal make" style="display: flex;">
+          <div class="modal-box">
+              <div class="header">
+                <div class="title">Word 문서</div>
+                <div class="x-button">
+                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('make')"></span>
+                </div>
+              </div>
+              <div class="modal-content">
+                <div>이름</div>
+                <div style="display:flex; align-items:flex-end; user-select:none;">
+                  <input type="text" placeholder="새 이름을 입력하세요." style="width:'calc(100% - 35px)';" autocomplete='off' onkeyup="enterKey(()=>makeItems('word_input', 'Word 문서', '/api/makeWord', '.docx'), event)" name="change_name" id="word_input" value="">&nbsp;.docx
+                </div>
+              </div>
+              <div class="button-box">
+                  <div class="name-btn" onclick="makeItems('word_input', 'Word 문서', '/api/makeWord', '.docx')">만들기</div>
+                  <div class="cancel-btn" onclick="modalClose('make')">취소</div>
+              </div>
+          </div>
+      </div>`);
+      $('body').append(modalContainer);
+      $('#word_input').focus();
+  }
+
+  function mkPpt() {
+    const modalContainer = $(
+      `<div class="modal make" style="display: flex;">
+          <div class="modal-box">
+              <div class="header">
+                <div class="title">PowerPoint 프레젠테이션</div>
+                <div class="x-button">
+                  <span><img src="/static/images/x-button.png" width="15" height="15" alt="닫기 버튼" onclick="modalClose('make')"></span>
+                </div>
+              </div>
+              <div class="modal-content">
+                <div>이름</div>
+                <div style="display:flex; align-items:flex-end; user-select:none;">
+                  <input type="text" placeholder="새 이름을 입력하세요." style="width:'calc(100% - 35px)';" autocomplete='off' onkeyup="enterKey(()=>makeItems('ppt_input', 'PowerPoint 프레젠테이션', '/api/makePptx', '.pptx'), event)" name="change_name" id="ppt_input" value="">&nbsp;.docx
+                </div>
+              </div>
+              <div class="button-box">
+                  <div class="name-btn" onclick="makeItems('ppt_input', 'PowerPoint 프레젠테이션', '/api/makePptx', '.pptx')">만들기</div>
+                  <div class="cancel-btn" onclick="modalClose('make')">취소</div>
+              </div>
+          </div>
+      </div>`);
+      $('body').append(modalContainer);
+      $('#ppt_input').focus();
   }
 
-  function modalClose(className){
+  function enterKey(method, event) {
+    if (event.key === 'Enter' && $('.modal.alert').length === 0) {
+      method();
+    } 
+  }
+  
+  function modalClose(className, id){
       $('.modal.' + className).remove();
+      if(id) {
+        $('#' + id).focus();
+      }
   }
 
   function getSitesId() {
@@ -1965,9 +2046,7 @@
       },
       success : (res)=> {
           if (res.success === 'S') {
-            const selectDrive = $('.panel-item.on');
-            selectDrive.removeClass('on');
-            selectDrive.click();
+            refreshDrives();
           }
           alertMessage('이름 변경',res.message);
       },
@@ -1978,4 +2057,10 @@
   }
 
 
+  function refreshDrives() {
+    const selectDrive = $('.panel-item.on');
+    selectDrive.removeClass('on');
+    selectDrive.click();
+  }
+
 </script>