Bläddra i källkod

Add COS Cloud and Settings

zhuzhuyule 7 år sedan
förälder
incheckning
62909b4c29

+ 46 - 28
app/moe-app.js

@@ -22,32 +22,52 @@
 'use strict';
 
 const MoeditorWindow = require('./moe-window'),
-      MoeditorAction = require('./moe-action'),
-      MoeditorFile = require('./moe-file'),
-      shortcut = require('electron-localshortcut'),
-      MoeditorLocale = require('./moe-l10n'),
-      MoeditorShell = require('./moe-shell'),
-      QiniuServer = require('./hexo-qiniu'),
-      MoeditorAbout = require('./moe-about'),
-      MoeditorSettings = require('./moe-settings'),
-      fs = require('fs'),
-      path = require('path');
+    MoeditorAction = require('./moe-action'),
+    MoeditorFile = require('./moe-file'),
+    shortcut = require('electron-localshortcut'),
+    MoeditorLocale = require('./moe-l10n'),
+    MoeditorAbout = require('./moe-about'),
+    MoeditorSettings = require('./moe-settings'),
+    fs = require('fs'),
+    path = require('path');
+
+let shellServer = false;
+let qiniuServer = false;
+let cosServer = false;
 
 class MoeditorApplication {
-	constructor() {
-		this.windows = new Array();
+    constructor() {
+        this.windows = new Array();
         this.hexoWindow = null;
-	}
+    }
 
-	open(fileName,defName) {
+    open(fileName, defName) {
         if (typeof fileName === 'undefined') {
-            this.windows.push(new MoeditorWindow(process.cwd(),defName));
+            this.windows.push(new MoeditorWindow(process.cwd(), defName));
         } else {
-            this.windows.push(new MoeditorWindow(fileName,defName));
+            this.windows.push(new MoeditorWindow(fileName, defName));
         }
-	}
+    }
 
-	run() {
+    getShellServer() {
+        if (!shellServer)
+            shellServer = new (require('./tool/hexo-shell'))();
+        return shellServer;
+    }
+
+    getQiniuServer() {
+        if (!qiniuServer)
+            qiniuServer = new (require('./tool/hexo-qiniu'))();
+        return qiniuServer;
+    }
+
+    getCOSServer() {
+        if (!cosServer)
+            cosServer = new (require('./tool/hexo-cos'))();
+        return cosServer;
+    }
+
+    run() {
         global.Const = require('./moe-const');
 
         app.setName(Const.name);
@@ -57,8 +77,6 @@ class MoeditorApplication {
         this.Const = Const;
 
         this.locale = new MoeditorLocale();
-        this.shellServer = new MoeditorShell();
-        this.qiniuServer = new QiniuServer();
         global.__ = str => this.locale.get(str);
 
         this.flag = new Object();
@@ -92,7 +110,7 @@ class MoeditorApplication {
         if (typeof this.osxOpenFile === 'string') docs.push(this.osxOpenFile);
 
         if (docs.length == 0) this.open();
-		else for (var i = 0; i < docs.length; i++) {
+        else for (var i = 0; i < docs.length; i++) {
             docs[i] = path.resolve(docs[i]);
             this.addRecentDocument(docs[i]);
             this.open(docs[i]);
@@ -102,7 +120,7 @@ class MoeditorApplication {
         else this.registerShortcuts();
 
         this.listenSettingChanges();
-	}
+    }
 
     registerAppMenu() {
         require('./moe-menu')(
@@ -225,24 +243,24 @@ class MoeditorApplication {
         app.addRecentDocument(path);
     }
 
-    getHighlightThemesDir(){
+    getHighlightThemesDir() {
         const currTheme = this.config.get('render-theme')
         let themedir = 'github'
-        if (!(currTheme == '*GitHub' || currTheme == '*No Theme')){
+        if (!(currTheme == '*GitHub' || currTheme == '*No Theme')) {
             if (currTheme.startsWith('*'))
                 themedir = currTheme.slice(1).toLowerCase();
             else
                 themedir = currTheme.toLowerCase();
         }
-        themedir = path.join(moeApp.Const.path+'/views/highlightThemes/',themedir);
-	    return (fs.existsSync(themedir)? themedir : '');
+        themedir = path.join(moeApp.Const.path + '/views/highlightThemes/', themedir);
+        return (fs.existsSync(themedir) ? themedir : '');
     }
 
-    getHexo(){
+    getHexo() {
         return this.hexo;
     }
 
-    setHexo(hexo){
+    setHexo(hexo) {
         this.hexo = hexo;
     }
 }

+ 5 - 0
app/moe-config-default.js

@@ -52,4 +52,9 @@ module.exports = {
     'image-qiniu-bucket':'',
     'image-qiniu-url-protocol': 'http://',
     'image-qiniu-url':'',
+    'image-cos-accessKey':'',
+    'image-cos-secretKey':'',
+    'image-cos-bucket':'|',
+    'image-cos-url-protocol': 'http://',
+    'image-cos-url':'',
 };

+ 8 - 2
app/moe-l10n.js

@@ -81,7 +81,7 @@ class MoeditorLocale {
 	get(str) {
         let res;
 		if (typeof strings[this.locale] === 'undefined' || typeof strings[this.locale][str] === 'undefined') {
-            res = strings['en'][str];
+            res = strings['en'][str] || str;
             console.log('Localization of "' + str + '" in "' + this.locale + '" failed, falling back to English.');
         } else {
             res = strings[this.locale][str];
@@ -215,6 +215,7 @@ const strings = {
         "Web Type": "Web Type",
         "WebSM": "WebSM",
         "QiNiu": "QiNiu",
+        "WebCOS": "Tencent",
         "AccessKey": "Access Key",
         "SecretKey": "Secret Key",
         "Bucket": "Bucket",
@@ -229,6 +230,7 @@ const strings = {
         "Delete": "Delete",
         "Select All": "Select All",
 
+        "UploadToCOS": "Upload To COS",
         "UploadToSMMS": "Upload To SM.MS",
         "UploadToQiNiu": "Upload To QiNiu",
         "Quick Open":"Quick Open",
@@ -238,6 +240,7 @@ const strings = {
         "OpenPathHEXO":"Open Hexo Path",
         "WebIndex":"Open Your Index",
         "WebLocalIndex":"Open Local Index",
+        "WebCOSSource":"Open COS Src Index",
         "WebQiNiuSource":"Open QiNiu Src Index",
         "WebSMMS":"Open SM.MS Index",
 
@@ -386,6 +389,7 @@ const strings = {
         "Web Type": "云图类型",
         "WebSM": "SM.MS 图床",
         "QiNiu": "七牛云",
+        "WebCOS": "腾讯云",
         "AccessKey": "Access Key",
         "SecretKey": "Secret Key",
         "Bucket": "存储空间",
@@ -399,6 +403,7 @@ const strings = {
         "Delete": "删除",
         "Select All": "全选",
 
+        "UploadToCOS": "上传腾讯云",
         "UploadToSMMS": "上传SM.MS",
         "UploadToQiNiu": "上传七牛",
         "Quick Open":"快速打开",
@@ -408,7 +413,8 @@ const strings = {
         "OpenPathHEXO":"Hexo项目路径",
         "WebIndex":"主页",
         "WebLocalIndex":"本地主页",
-        "WebQiNiuSource":"七牛存储主页",
+        "WebCOSSource":"腾讯云主页",
+        "WebQiNiuSource":"七牛云主页",
         "WebSMMS":"SM.MS主页",
 
         "Services": "服务",

+ 4 - 4
app/moe-window.js

@@ -97,15 +97,15 @@ class MoeditorWindow {
                 }
             }
 
-            console.log(this.window == moeApp.shellServer.lastWindow)
-            if (this.window == moeApp.shellServer.lastWindow)
-                process.nextTick(moeApp.shellServer.kill,false);
+            console.log(this.window == moeApp.getShellServer().lastWindow)
+            if (this.window == moeApp.getShellServer().lastWindow)
+                process.nextTick(moeApp.getShellServer().kill,false);
 
             const index = moeApp.windows.indexOf(this);
             if (index !== -1) moeApp.windows.splice(index, 1);
 
             if ( !moeApp.windows.length) {
-                process.nextTick(moeApp.shellServer.kill,false);
+                process.nextTick(moeApp.getShellServer().kill,false);
                 setTimeout(app.quit,200)
             }
         });

+ 230 - 0
app/tool/hexo-cos.js

@@ -0,0 +1,230 @@
+let fs = require('fs');
+let path = require('path');
+
+let config = {
+    SecretId: '',
+    SecretKey: '',
+    Bucket: '',
+    Region: '',
+    Protocol: 'http://'
+};
+let TaskId;
+
+class COSServer {
+    constructor(acessKey, secretKey) {
+        acessKey = acessKey || moeApp.config.get('image-cos-accessKey');
+        secretKey = secretKey || moeApp.config.get('image-cos-secretKey');
+        config.SecretId = acessKey;
+        config.SecretKey = secretKey;
+        this.cos = new (require('cos-nodejs-sdk-v5'))({
+            // 必选参数
+            SecretId: acessKey,
+            SecretKey: secretKey,
+            // 可选参数
+            FileParallelLimit: 5,    // 控制文件上传并发数
+            ChunkParallelLimit: 5,   // 控制单个文件下分片上传并发数
+            ChunkSize: 2 * 1024 * 1024,  // 控制分片大小,单位 B
+        });
+    }
+
+    /**
+     * 更新信息
+     * @param acessKey
+     * @param secretKey
+     * @param bucket
+     * @param region
+     */
+    update(acessKey, secretKey, bucket, region,protocol) {
+        this.cos.options.SecretId = acessKey || config.SecretId || '';
+        this.cos.options.SecretKey = secretKey || config.SecretKey || '';
+        config.SecretId = acessKey || config.SecretId || '';
+        config.SecretKey = secretKey || config.SecretKey || '';
+        config.Bucket = bucket || config.Bucket || '';
+        config.Region = region || config.Region || '';
+        config.Protocol = protocol || config.Protocol || 'http://';
+    }
+
+    getService(cb) {
+        this.cos.getService(function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    getFileURL(key, cb) {
+        let url = this.cos.getObjectUrl({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: key,
+            Expires: 60,
+            Sign: true,
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+        return url;
+    }
+
+    getBucketLocation(cb) {
+        this.cos.getBucketLocation({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    getBucketVersioning(cb) {
+        this.cos.getBucketVersioning({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    putObjectCopy(filename, cb) {
+        this.cos.putObjectCopy({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: filename,
+            CopySource: config.Bucket + '.cos.' + config.Region + '.myqcloud.com/' + filename
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    download(serverFile, localFile, cb) {
+        let localName = localFile + path.basename(serverFile);
+        this.cos.getObject({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: serverFile,
+            Output: localName,
+            onProgress: function (progressData) {
+                console.log(JSON.stringify(progressData));
+            }
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    deleteObject(fileanme, cb) {
+        this.cos.deleteObject({
+            Bucket: config.Bucket, // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: fileanme
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    deleteMultipleObject(fileanmes, cb) {
+        this.cos.deleteMultipleObject({
+            Bucket: config.Bucket,
+            Region: config.Region,
+            Objects: fileanmes  // [
+                                //     {Key: '1mb.zip'},
+                                //     {Key: '3mb.zip'},
+                                // ]
+        }, function (err, data) {
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    uploadFile(localFile, serverFile, cb) {
+        if (!serverFile)
+            serverFile = path.basename(localFile);
+        this.cos.putObject({
+            Bucket: config.Bucket, /* 必须 */ // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: serverFile, /* 必须 */
+            TaskReady: function (tid) {
+                TaskId = tid;
+            },
+            onProgress: function (progressData) {
+                console.log(JSON.stringify(progressData));
+            },
+            // 格式1. 传入文件内容
+            // Body: fs.readFileSync(filepath),
+            // 格式2. 传入文件流,必须需要传文件大小
+            Body: fs.createReadStream(localFile),
+            ContentLength: fs.statSync(localFile).size
+        }, function (err, data) {
+            /*data = {
+            *  ETag: "b2b35ae16725f9578f58ebe98f53bb4d",
+            *  Location: "http://myblog-1256010832.cos.ap-chengdu.myqcloud.com/test.png",
+            *  statusCode: 200,
+            *  headers: {}
+            }*/
+            if (typeof cb === 'function')
+                cb(err || data);
+        });
+    }
+
+    sliceUploadFile(localFile, serverFile, cb) {
+        if (!serverFile)
+            serverFile = path.basename(localFile);
+        this.cos.sliceUploadFile({
+            Bucket: config.Bucket, /* 必须 */ // Bucket 格式:test-1250000000
+            Region: config.Region,
+            Key: serverFile, /* 必须 */
+            FilePath: localFile, /* 必须 */
+            TaskReady: function (tid) {
+                TaskId = tid;
+            },
+            onHashProgress: function (progressData) {
+                console.log(JSON.stringify(progressData));
+            },
+            onProgress: function (progressData) {
+                console.log(JSON.stringify(progressData));
+            },
+        }, function (err, data) {
+            /*data = {
+            *  Bucket: "myblog",
+            *  ETag: "",
+            *  Key: "test.png",
+            *  Location: "",
+            *  statusCode: 200,
+            *  headers: {}
+            }*/
+            if (typeof cb === 'function'){
+                let response = {};
+                if (err){
+                    response.code = 'error';
+                    response.error = err.error.message;
+                } else{
+                    response.code = 'success';
+                    response.data = {
+                        url: config.Protocol + data.Location,
+                        hash: data.Key
+                    }
+                }
+                cb(localFile,response,data);
+            }
+        });
+    }
+
+    cancelTask() {
+        this.cos.cancelTask(TaskId);
+        console.log('canceled');
+    }
+
+    pauseTask() {
+        this.cos.pauseTask(TaskId);
+        console.log('paused');
+    }
+
+    restartTask() {
+        this.cos.restartTask(TaskId);
+        console.log('restart');
+    }
+}
+
+module.exports = COSServer;

+ 18 - 14
app/hexo-qiniu.js → app/tool/hexo-qiniu.js

@@ -15,6 +15,8 @@ class qiniuServer {
      * @param url
      */
     update(acessKey, secretKey, bucket, url) {
+        acessKey = acessKey || moeApp.config.get('image-qiniu-accessKey');
+        secretKey = secretKey || moeApp.config.get('image-qiniu-secretKey');
         this.qiniu.conf.ACCESS_KEY = acessKey;
         this.qiniu.conf.SECRET_KEY = secretKey;
         this.mac = new this.qiniu.auth.digest.Mac(acessKey, secretKey);
@@ -50,7 +52,7 @@ class qiniuServer {
      */
     getBuckets(callback) {
         const url_api_bukets = 'https://rs.qbox.me/buckets';
-        let XMLHttpRequest = require('./tool/XMLHttpRequest').XMLHttpRequest;
+        let XMLHttpRequest = require('./XMLHttpRequest').XMLHttpRequest;
         let xhr = new XMLHttpRequest();
         xhr.open('get', url_api_bukets);
         xhr.setRequestHeader('Authorization', this.getAccessToken(url_api_bukets));
@@ -74,7 +76,7 @@ class qiniuServer {
      */
     getBucketsUrl(buketName,callback) {
         const url_api_bukets = 'https://api.qiniu.com/v6/domain/list?tbl=' + buketName;
-        let XMLHttpRequest = require('./tool/XMLHttpRequest').XMLHttpRequest;
+        let XMLHttpRequest = require('./XMLHttpRequest').XMLHttpRequest;
         let xhr = new XMLHttpRequest();
         xhr.open('get', url_api_bukets);
         xhr.setRequestHeader('Authorization', this.getAccessToken(url_api_bukets));
@@ -101,7 +103,7 @@ class qiniuServer {
         if (!buketName) return;
         const url_api_bukets = require('util').format(
             'https://rsf.qbox.me/list?bucket=%s&marker=&limit=1&prefix=%s&delimiter=/', buketName, prefix || '')
-        let XMLHttpRequest = require('./tool/XMLHttpRequest').XMLHttpRequest;
+        let XMLHttpRequest = require('./XMLHttpRequest').XMLHttpRequest;
         let xhr = new XMLHttpRequest();
         xhr.open('get', url_api_bukets);
         xhr.setRequestHeader('Authorization', this.getAccessToken(url_api_bukets));
@@ -132,23 +134,25 @@ class qiniuServer {
         var extra = new this.qiniu.form_up.PutExtra();
         let qiniuServer = this;
         formUploader.putFile(token, serverFileName, localFile, extra,
-            function (error,data, responseInfo) {
-                if (error)
-                    console.log( error);
-                if (responseInfo.statusCode == 200 || responseInfo.statusCode == 579) {
-                    let result = {
+            function (respErr, respBody, respInfo) {
+                console.log( respBody);
+                if (respErr) {
+                    throw respErr;
+                }
+                if (respInfo.statusCode == 200 || respInfo.statusCode == 579) {
+                    let response = {
                         code: 'success',
                         data: {
-                            url: qiniuServer.url + data.key
+                            url: qiniuServer.url + respBody.key
                         }
                     }
-                    callback(localFile, result);
+                    callback(localFile, response);
                 } else {
-                    console.log(responseInfo.statusCode);
-                    let result = {
-                        error: responseInfo.statusCode + responseInfo,
+                    console.log(respInfo.statusCode);
+                    let response = {
+                        error: respInfo.statusCode + respBody,
                     }
-                    callback(localFile, result);
+                    callback(localFile, response);
                 }
             });
     }

+ 32 - 31
app/moe-shell.js → app/tool/hexo-shell.js

@@ -50,17 +50,18 @@
 
 
 const Promise = require('bluebird');
-const {ipcMain} = require('electron');
-const thread_kill = require('./tool/thread_kill');
+const thread_kill = require('./thread_kill');
 const exec = require('child_process').exec;
 const util = require('util');
 
+let shellServer = false;
 class ShellServer {
     constructor() {
         this.shellProcess = null;
         this.isForce = false;
         this.oldbiu = null;
         this.drags = null;
+        shellServer = this;
     }
 
     processRunning() {
@@ -68,9 +69,9 @@ class ShellServer {
     }
 
     sendConsole(content, type, btnTip) {
-        if (moeApp.shellServer.closeMsg) return;
+        if (shellServer.closeMsg) return;
         try {
-            moeApp.shellServer.lastWindow.hexoeditorWindow.window.webContents.send('pop-message-shell', {
+            shellServer.lastWindow.hexoeditorWindow.window.webContents.send('pop-message-shell', {
                 subProcess: this.shellProcess,
                 content: content,
                 type: type,
@@ -84,52 +85,52 @@ class ShellServer {
     execCmd(command) {
         console.log('execute', command);
         let flagOK = false;
-        clearTimeout(moeApp.shellServer.timeID);
-        moeApp.shellServer.closeMsg = false;
-        moeApp.shellServer.lastWindow = require('electron').BrowserWindow.getFocusedWindow();
-        moeApp.shellServer.isForce = false;
-        moeApp.shellServer.shellProcess = exec(command, {cwd: moeApp.hexo.config.__basedir});
-        moeApp.shellServer.sendConsole('<i class="fa fa-spinner fa-pulse fa-fw margin-bottom"></i>'+__("Executing"), 'info', 'ban');
-        moeApp.shellServer.shellProcess.stderr.on('data', (data) => {
+        clearTimeout(shellServer.timeID);
+        shellServer.closeMsg = false;
+        shellServer.lastWindow = require('electron').BrowserWindow.getFocusedWindow();
+        shellServer.isForce = false;
+        shellServer.shellProcess = exec(command, {cwd: moeApp.hexo.config.__basedir});
+        shellServer.sendConsole('<i class="fa fa-spinner fa-pulse fa-fw margin-bottom"></i>'+__("Executing"), 'info', 'ban');
+        shellServer.shellProcess.stderr.on('data', (data) => {
             console.log(data);
         });
-        moeApp.shellServer.shellProcess.stdout.on('data', (data) => {
-            clearTimeout(moeApp.shellServer.timeID);
+        shellServer.shellProcess.stdout.on('data', (data) => {
+            clearTimeout(shellServer.timeID);
             console.log(data);
             if ( /INFO  Hexo is running at https?:\/+/.test(data) ) {
                 flagOK = true;
-                moeApp.shellServer.sendConsole('<i class="fa fa-spinner fa-pulse fa-fw margin-bottom"></i>'+__('ServerStart'), 'info', 'stop');
+                shellServer.sendConsole('<i class="fa fa-spinner fa-pulse fa-fw margin-bottom"></i>'+__('ServerStart'), 'info', 'stop');
                 require('electron').shell.openExternal(data.match(/INFO  Hexo is running at (https?:\/+[^\/]+\/). Press Ctrl.C to stop./i)[1])
             } else {
-                moeApp.shellServer.timeID = setTimeout(() => {
+                shellServer.timeID = setTimeout(() => {
                     if (!flagOK) {
-                        moeApp.shellServer.kill(moeApp.shellServer.shellProcess)
+                        shellServer.kill(shellServer.shellProcess)
                         flagOK = -1;
                     }
                 }, 10000)
             }
         });
-        moeApp.shellServer.shellProcess.on('close', (code, signal) => {
+        shellServer.shellProcess.on('close', (code, signal) => {
             if (flagOK === -1)
-                moeApp.shellServer.sendConsole(__('Operation Execution Timeout'), 'danger', 'close');
-            else if (moeApp.shellServer.isForce)
-                moeApp.shellServer.sendConsole(__('Operation Canceled'), 'success', 'check');
+                shellServer.sendConsole(__('Operation Execution Timeout'), 'danger', 'close');
+            else if (shellServer.isForce)
+                shellServer.sendConsole(__('Operation Canceled'), 'success', 'check');
             else if (code == 0)
-                moeApp.shellServer.sendConsole(__('Operation Finished'), 'success', 'check');
-            moeApp.shellServer.shellProcess = null;
+                shellServer.sendConsole(__('Operation Finished'), 'success', 'check');
+            shellServer.shellProcess = null;
         });
-        moeApp.shellServer.shellProcess.on('error', err => {
-            if (moeApp.shellServer.shellProcess)
-                moeApp.shellServer.sendConsole(err, 'danger', 'close')
+        shellServer.shellProcess.on('error', err => {
+            if (shellServer.shellProcess)
+                shellServer.sendConsole(err, 'danger', 'close')
             console.error(err);
         })
     }
 
     kill(subProcess) {
-        moeApp.shellServer.isForce = true;
-        moeApp.shellServer.closeMsg = (typeof subProcess === "boolean");
+        shellServer.isForce = true;
+        shellServer.closeMsg = (typeof subProcess === "boolean");
         if (!subProcess)
-            subProcess = moeApp.shellServer.shellProcess
+            subProcess = shellServer.shellProcess
         if (subProcess)
             thread_kill(subProcess.pid, function (err) {
                 console.error(err);
@@ -137,8 +138,8 @@ class ShellServer {
     }
 
     checkPort(ip, port) {
-        moeApp.shellServer.isForce = false;
-        moeApp.shellServer.closeMsg = false;
+        shellServer.isForce = false;
+        shellServer.closeMsg = false;
         return new Promise(function (resolve, reject) {
             if (port > 65535 || port < 1) {
                 return reject(new Error('Port number ' + port + ' is invalid. Try a number between 1 and 65535.'));
@@ -156,7 +157,7 @@ class ShellServer {
 
     serverFail(err) {
         if (err.code === 'EADDRINUSE') { // 端口Ip地址占用
-            moeApp.shellServer.sendConsole(util.format(__('PortOccupied'),err.address,err.port), 'danger', 'close');
+            shellServer.sendConsole(util.format(__('PortOccupied'),err.address,err.port), 'danger', 'close');
         }
     }
 

+ 1 - 0
package.json

@@ -68,6 +68,7 @@
     "cheerio": "^1.0.0-rc.2",
     "codemirror": "^5.18.2",
     "configstore": "^2.1.0",
+    "cos-nodejs-sdk-v5": "^2.2.6",
     "electron-localshortcut": "^0.6.1",
     "electron-titlebar": "0.0.2",
     "flowchart.js": "^1.6.3",

+ 49 - 8
views/main/hexo-image.js

@@ -84,7 +84,7 @@ class ImgManager {
     getQiNiuServer() {
         if (!this.qiniuServer) {
             // this.qiniuServer = new (require('./hexo-qiniu'))();
-            this.qiniuServer = moeApp.qiniuServer;
+            this.qiniuServer = moeApp.getQiniuServer();
             this.qiniuServer.update(
                 moeApp.config.get('image-qiniu-accessKey'),
                 moeApp.config.get('image-qiniu-secretKey'),
@@ -95,6 +95,23 @@ class ImgManager {
         return this.qiniuServer;
     }
 
+    getCOSServer() {
+        if (!this.cosServer) {
+            // this.qiniuServer = new (require('./hexo-qiniu'))();
+            this.cosServer = moeApp.getCOSServer();
+            let bucketObj = moeApp.config.get('image-cos-bucket');
+            bucketObj = (bucketObj||"|").split('|');
+            this.cosServer.update(
+                moeApp.config.get('image-cos-accessKey'),
+                moeApp.config.get('image-cos-secretKey'),
+                bucketObj[0],
+                bucketObj[1],
+                moeApp.config.get('image-cos-url-protocol')
+            );
+        }
+        return this.cosServer;
+    }
+
     getImageOfPath(imgPath, md5ID) {
         if (fs.existsSync(imgPath)) {
             imgPath = imgPath.replace(/\\/g, '/');
@@ -238,19 +255,28 @@ class ImgManager {
         this.getQiNiuServer().uploadFile(imgPath,this.relativePath(imgPath).slice(1),callback);
     }
 
+    asyncUploadToCOS(imgPath, callback) {
+        this.getCOSServer().sliceUploadFile(imgPath,this.relativePath(imgPath).slice(1),callback);
+    }
+
     asyncUploadFile(imgPath, callback) {
-        if (this.isQiNiu){
-            this.asyncUploadToQiNiu(imgPath,callback)
-        } else {
-            let file = new File([fs.readFileSync(imgPath)], md5(imgPath) + path.extname(imgPath), {type: 'image/' + path.extname(imgPath).slice(1)});
-            return this.asyncUploadToSm(imgPath,file, callback);
+        switch (this.type){
+            case 'qiniu':
+                this.asyncUploadToQiNiu(imgPath,callback)
+                break;
+            case 'cos':
+                this.asyncUploadToCOS(imgPath,callback)
+                break;
+            default:
+                let file = new File([fs.readFileSync(imgPath)], md5(imgPath) + path.extname(imgPath), {type: 'image/' + path.extname(imgPath).slice(1)});
+                this.asyncUploadToSm(imgPath,file, callback);
         }
     }
 
     uploadLocalSrc() {
         this.isUploading = true;
         this.timeout = 0;
-        this.isQiNiu = moeApp.config.get('image-web-type') == 'qiniu';
+        this.type = moeApp.config.get('image-web-type');
 
         let finishedCount = 0;
         let uploadList = new Map();
@@ -283,9 +309,24 @@ class ImgManager {
             uploadEnd();
         }
 
-        function uploadRequest(fileID, response) {
+        /**
+         * 回调函数
+         * @param fileID
+         * @param response
+         *
+         * response={
+         *    code: 'success'|'error'
+         *    data: {
+         *        url: 'http....',
+         *        hash: '.....'
+         *          }
+         *    error: 'error'
+         * }
+         */
+        function uploadRequest(fileID, response,info) {
             finishedCount++;
             console.log(finishedCount + '/' + uploadList.size)
+            if (info) console.log(info)
             if (response.code == 'success') {
                 successList.set(fileID, response)
             } else {

+ 36 - 28
views/main/moe-contextmenu.js

@@ -19,14 +19,13 @@
 
 'use strict'
 
-let shellServer = moeApp.shellServer;
 
 document.addEventListener('DOMContentLoaded', () => {
     const remote = require('electron').remote;
     const {Menu, MenuItem} = remote;
 
     const editor = document.getElementById('editor'), containerWrapper = document.getElementById('preview');
-
+    let shellServer = moeApp.getShellServer();
     window.addEventListener('contextmenu', (e) => {
         e.preventDefault();
         if (editor.contains(e.target) || containerWrapper.contains(e.target)) {
@@ -35,7 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
                 {
                     label: __('Undo'),
                     enabled: window.editor.doc.historySize().undo !== 0,
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         window.editor.undo();
                     }
                 },
@@ -56,14 +55,14 @@ document.addEventListener('DOMContentLoaded', () => {
                     label: __('Paste'),
                     enabled: inEditor && (require('electron').clipboard.readText().length !== 0 ||
                         !clipboard.readImage().isEmpty()),
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         pasteData();
                     }
                 },
                 {
                     label: __('Delete'),
                     enabled: inEditor && window.editor.doc.somethingSelected(),
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         hexoWindow.webContents.sendInputEvent({type: 'keyDown', modifiers: [], keyCode: 'Delete'});
                         hexoWindow.webContents.sendInputEvent({type: 'keyUp', modifiers: [], keyCode: 'Delete'});
                     }
@@ -73,7 +72,7 @@ document.addEventListener('DOMContentLoaded', () => {
                 },
                 {
                     label: __('Select All'),
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         if (inEditor) {
                             window.editor.execCommand('selectAll');
                         } else {
@@ -92,7 +91,7 @@ document.addEventListener('DOMContentLoaded', () => {
                     label: __('Show Number'),
                     type: 'checkbox',
                     checked: window.editor.getOption('lineNumbers'),
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         let editor = document.querySelector('#editor');
                         if (item.checked) {
                             editor.classList.add('gutter');
@@ -107,7 +106,7 @@ document.addEventListener('DOMContentLoaded', () => {
                     label: __('Scroll Sync'),
                     type: 'checkbox',
                     checked: window.scrollTogether,
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         window.scrollTogether = !window.scrollTogether;
                     }
                 },
@@ -115,9 +114,9 @@ document.addEventListener('DOMContentLoaded', () => {
                     type: 'separator',
                 },
                 {
-                    label: (moeApp.config.get('image-web-type')=='qiniu')? __('UploadToQiNiu'): __('UploadToSMMS'),
+                    label: (moeApp.config.get('image-web-type')=='qiniu')? __('UploadToQiNiu'):((moeApp.config.get('image-web-type')=='cos')?__('UploadToCOS'):__('UploadToSMMS')),
                     enabled: !imgManager.isUploading,
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         !imgManager.uploadLocalSrc();
                     }
                 },
@@ -127,15 +126,15 @@ document.addEventListener('DOMContentLoaded', () => {
                         {
                             label: __('OpenPathPost'),
                             enabled: !!hexoWindow.fileName ,
-                            click(item, hexoWindow) {
-                                const shell = require('electron').shell
+                            click(item, w) {
+                                const shell = require('electron').shell;
                                 shell.showItemInFolder(hexoWindow.fileName)
                             }
                         },
                         {
                             label: __('OpenPathPostSrc'),
                             enabled: !!(imgManager && imgManager.imgBaseDir),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 let dir = path.join(imgManager.imgPathDir);
                                 if (fs.existsSync(dir))
@@ -147,7 +146,7 @@ document.addEventListener('DOMContentLoaded', () => {
                         {
                             label: __('OpenPathSrcCenter'),
                             enabled: !!(imgManager && imgManager.imgBaseDir),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 shell.showItemInFolder(path.join(imgManager.imgBaseDir, '*'))
                             }
@@ -155,7 +154,7 @@ document.addEventListener('DOMContentLoaded', () => {
                         {
                             label: __('OpenPathHEXO'),
                             enabled: moeApp.useHexo,
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell
                                 shell.showItemInFolder(path.join(hexo.config.__basedir, '*'))
                             }
@@ -166,7 +165,7 @@ document.addEventListener('DOMContentLoaded', () => {
                         {
                             label: __('WebIndex'),
                             enabled: moeApp.useHexo && (!!hexo.config.url),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 let weburl = hexo.config.url;
                                 weburl = (weburl.startsWith('http://') || weburl.startsWith('https://') ? weburl : 'http://' + weburl);
@@ -178,7 +177,7 @@ document.addEventListener('DOMContentLoaded', () => {
                         {
                             label: __('WebLocalIndex'),
                             enabled: moeApp.useHexo && (shellServer.shellProcess != null),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 let weburl = url.parse('http://localhost:4000');
                                 weburl.hostname = moeApp.hexo.config.server.ip || 'localhost';
@@ -188,14 +187,23 @@ document.addEventListener('DOMContentLoaded', () => {
                         },
                         {
                             label: __('WebQiNiuSource'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 shell.openExternal(`https://portal.qiniu.com/bucket/${moeApp.config.get('image-qiniu-bucket')}/resource`)
                             }
                         },
+                        {
+                            label: __('WebCOSSource'),
+                            click(item, w) {
+                                const shell = require('electron').shell;
+                                let bucketObj = moeApp.config.get('image-cos-bucket');
+                                bucketObj = (bucketObj||"|").split('|');
+                                shell.openExternal(`https://console.cloud.tencent.com/cos5/bucket/setting?type=filelist&bucketName=${bucketObj[0]}&path=&region=${bucketObj[1]}`)
+                            }
+                        },
                         {
                             label: __('WebSMMS'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 const shell = require('electron').shell;
                                 shell.openExternal(`https://sm.ms/`)
                             }
@@ -207,23 +215,23 @@ document.addEventListener('DOMContentLoaded', () => {
                     visible: moeApp.useHexo,
                 },
                 {
-                    label: "HEXO",
+                    label: "Hexo",
                     visible: moeApp.useHexo,
                     enabled: !shellServer.processRunning(),
-                    click(item, hexoWindow) {
+                    click(item, w) {
                         const shell = require('electron').shell
                         shell.showItemInFolder(path.join(hexo.config.__basedir, '*'))
                     },
                     submenu: [
                         {
                             label: __('File Rename'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 window.changeFileName(true);
                             }
                         },
                         {
                             label: __('HEXOQuickPublish'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.generalAndDeploy();
                             }
                         },
@@ -232,31 +240,31 @@ document.addEventListener('DOMContentLoaded', () => {
                         },
                         {
                             label: __('HEXOServer'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.server();
                             }
                         },
                         {
                             label: __('HEXOClean'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.clean();
                             }
                         },
                         {
                             label: __('HEXOGenerate'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.general();
                             }
                         },
                         {
                             label: __('HEXODeploy'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.deploy();
                             }
                         },
                         {
                             label: __('HEXOKillPort'),
-                            click(item, hexoWindow) {
+                            click(item, w) {
                                 shellServer.stopServerForce();
                             }
                         }

+ 1 - 1
views/main/moe-popmessage.js

@@ -49,7 +49,7 @@ document.addEventListener('DOMContentLoaded', () => {
             closeButton: '<i class="fa fa-' + (arg.btnTip||"close") + '" aria-hidden="true" title=' + __(arg.btnTip||"Close") + '></i>'
         });
         oldbiu.closeButton.addEventListener('click', function () {
-            process.nextTick(moeApp.shellServer.kill);
+            process.nextTick(moeApp.getShellServer().kill);
         });
 
         if (!drags)

+ 42 - 0
views/main/moe-settings.js

@@ -232,6 +232,40 @@ function SetQiNiuWeb(val) {
     }
 }
 
+
+function SetCosAccessKey(val) {
+    if(imgManager && imgManager.cosServer){
+        imgManager.cosServer.update(val)
+    }
+}
+
+function SetCosSecretKey(val) {
+    if(imgManager && imgManager.cosServer){
+        imgManager.cosServer.update('',val)
+    }
+}
+
+
+function SetCosBucket(val) {
+    if(imgManager && imgManager.cosServer){
+        val = (val||"|").split('|');
+        imgManager.cosServer.update('','',val[0],val[1])
+    }
+}
+
+function SetCosWeb(val) {
+    if (val && val.oldURL && val.newURL){
+        let content = editor.getValue();
+        content = content.replace(new RegExp(val.oldURL,'g'),val.newURL);
+        editor.setValue(content);
+        hexoWindow.changed = true;
+        hexoWindow.content = content;
+    }
+    if(imgManager && imgManager.cosServer){
+        imgManager.cosServer.update('','','','',moeApp.config.get('image-cos-url-protocol'))
+    }
+}
+
 tryRun(setEditorFont, moeApp.config.get('editor-font'));
 tryRun(setShowLineNumber, !!moeApp.config.get('editor-ShowLineNumber'));
 tryRun(setScrollTogether, moeApp.config.get('scroll-Together'));
@@ -294,5 +328,13 @@ ipcRenderer.on('setting-changed', (e, arg) => {
         tryRun(SetQiNiuBucket, arg.val);
     } else if (arg.key === 'image-qiniu-url') {
         tryRun(SetQiNiuWeb, arg.val);
+    } else if (arg.key === 'image-cos-accessKey') {
+        tryRun(SetCosAccessKey, arg.val);
+    } else if (arg.key === 'image-cos-secretKey') {
+        tryRun(SetCosSecretKey, arg.val);
+    } else if (arg.key === 'image-cos-bucket') {
+        tryRun(SetCosBucket, arg.val);
+    } else if (arg.key === 'image-cos-url') {
+        tryRun(SetCosWeb, arg.val);
     }
 });

+ 173 - 20
views/settings/moe-settings.js

@@ -481,15 +481,60 @@ document.addEventListener('DOMContentLoaded', () => {
     let imageTabItem = document.querySelector('.item[data-tab="image"]');
     let imageTabContents = document.querySelector('.panel[data-tab="image"]');
     let imageType = imageTabContents.querySelector('#image-web-type');
+
+       //QiNiu
     let imageAccessKey = imageTabContents.querySelector('#image-qiniu-accessKey');
     let imageSecretKey = imageTabContents.querySelector('#image-qiniu-secretKey');
     let imageBucket = imageTabContents.querySelector('#image-qiniu-bucket');
     let imageBaseWebProtocol = imageTabContents.querySelector('#image-qiniu-url-protocol');
     let imageBaseWeb = imageTabContents.querySelector('#image-qiniu-url');
 
+    //腾讯
+    let imageCosAccessKey = imageTabContents.querySelector('#image-cos-accessKey');
+    let imageCosSecretKey = imageTabContents.querySelector('#image-cos-secretKey');
+    let imageCosBucket = imageTabContents.querySelector('#image-cos-bucket');
+    let imageCosBaseWebProtocol = imageTabContents.querySelector('#image-cos-url-protocol');
+    let imageCosBaseWeb = imageTabContents.querySelector('#image-cos-url');
+
+    imageType.addEventListener('change', function (e) {
+        moeApp.config.set(imageType.id, imageType.value);
+        let imageQiNiuItems = imageTabContents.querySelectorAll('tr[data-type="image-qiniu"]')
+        let imageCosItems = imageTabContents.querySelectorAll('tr[data-type="image-cos"]')
+        if (imageType.value == 'qiniu') {
+            imageAccessKey.value = moeApp.config.get(imageAccessKey.id);
+            imageSecretKey.value = moeApp.config.get(imageSecretKey.id);
+            checkBuckets();
+            imageCosItems.forEach((item) => {
+                item.style.display = 'none'
+            })
+            imageQiNiuItems.forEach((item) => {
+                item.style.display = 'table-row'
+            })
+        } else if (imageType.value == 'cos') {
+            imageCosAccessKey.value = moeApp.config.get(imageCosAccessKey.id);
+            imageCosSecretKey.value = moeApp.config.get(imageCosSecretKey.id);
+            checkCosBuckets();
+            imageQiNiuItems.forEach((item) => {
+                item.style.display = 'none'
+            })
+            imageCosItems.forEach((item) => {
+                item.style.display = 'table-row'
+            })
+        } else {
+            imageQiNiuItems.forEach((item) => {
+                item.style.display = 'none'
+            })
+            imageCosItems.forEach((item) => {
+                item.style.display = 'none'
+            })
+            ipcRenderer.send('setting-changed', {key: imageType.id, val: imageType.value});
+        }
+        imageTabItem.click();
+    })
+
     function hasQiNiuServer() {
         if (imageAccessKey.value && imageSecretKey.value && !global.qiniuServer) {
-            global.qiniuServer = moeApp.qiniuServer;
+            global.qiniuServer = moeApp.getQiniuServer();
             qiniuServer.update(
                 moeApp.config.get('image-qiniu-accessKey'),
                 moeApp.config.get('image-qiniu-secretKey'),
@@ -561,24 +606,6 @@ document.addEventListener('DOMContentLoaded', () => {
         }
     }
 
-    imageType.addEventListener('change', function (e) {
-        moeApp.config.set(imageType.id, imageType.value);
-        let imageQiNiuItems = imageTabContents.querySelectorAll('tr[data-type="image-qiniu"]')
-        if (imageType.value == 'qiniu') {
-            imageAccessKey.value = moeApp.config.get(imageAccessKey.id);
-            imageSecretKey.value = moeApp.config.get(imageSecretKey.id);
-            checkBuckets();
-            imageQiNiuItems.forEach((item) => {
-                item.style.display = 'table-row'
-            })
-        } else {
-            imageQiNiuItems.forEach((item) => {
-                item.style.display = 'none'
-            })
-            ipcRenderer.send('setting-changed', {key: imageType.id, val: imageType.value});
-        }
-        imageTabItem.click();
-    })
 
     imageAccessKey.addEventListener('blur', () => {
         imageAccessKey.type = 'password';
@@ -654,8 +681,134 @@ document.addEventListener('DOMContentLoaded', () => {
         ipcRenderer.send('setting-changed', {key: imageBaseWeb.id, val: value});
     })
 
+
+    //腾讯
+    function hasCosServer() {
+        if (imageCosAccessKey.value && imageCosSecretKey.value && !global.cosServer) {
+            global.cosServer = moeApp.getCOSServer();
+            let bucketObj = moeApp.config.get('image-cos-bucket');
+            bucketObj = (bucketObj||"|").split('|');
+            cosServer.update(
+                moeApp.config.get('image-cos-accessKey'),
+                moeApp.config.get('image-cos-secretKey'),
+                bucketObj[0],
+                bucketObj[1],
+                moeApp.config.get('image-cos-url-protocol')
+            );
+            return true;
+        }
+        return global.cosServer;
+    }
+
+    function checkCosBuckets() {
+        if (hasCosServer()) {
+            let oldBucket = moeApp.config.get(imageCosBucket.id);
+            cosServer.getService((response) => {
+                imageCosBucket.innerHTML = '';
+                if (response.statusCode == 200) {
+                    response.Buckets.forEach((bucket) => {
+                        let option = document.createElement('option');
+                        option.value = bucket.Name + '|' +bucket.Location;
+                        option.innerText = '['+bucket.Location+']  ' + bucket.Name ;
+                        imageCosBucket.appendChild(option);
+                        if ((option.value == oldBucket)) {
+                            option.selected = true;
+                            imageCosBucket.value = option.value;
+                        }
+                    })
+                    if (!imageCosBucket.value && imageCosBucket.firstChild) {
+                        imageCosBucket.value = imageCosBucket.firstChild.value;
+                    }
+                    if(imageCosBucket.value) {
+                        let event = new Event('change');
+                        imageCosBucket.dispatchEvent(event);
+                    }
+                } else {
+                    console.log(response,response.error.Message) ;
+                }
+            })
+        }
+    }
+
+    imageCosAccessKey.addEventListener('blur', () => {
+        imageCosAccessKey.type = 'password';
+        if (imageCosAccessKey.value && imageCosAccessKey.value.length == 36 && imageCosSecretKey.value && imageCosSecretKey.value.length == 32) {
+            let oldKey = moeApp.config.get(imageCosAccessKey.id);
+            if (oldKey != imageCosAccessKey.value) {
+                moeApp.config.set(imageCosAccessKey.id, imageCosAccessKey.value);
+                if (hasCosServer()) {
+                    cosServer.update(imageCosAccessKey.value, imageCosSecretKey.value)
+                    ipcRenderer.send('setting-changed', {key: imageCosAccessKey.id, val: imageCosAccessKey.value});
+                    checkCosBuckets();
+                }
+            }
+        }
+    })
+    imageCosAccessKey.addEventListener('focus', () => {
+        imageCosAccessKey.type = 'type';
+    })
+    imageCosSecretKey.addEventListener('blur', () => {
+        imageCosSecretKey.type = 'password';
+        if (imageCosAccessKey.value && imageCosAccessKey.value.length == 36 && imageCosSecretKey.value && imageCosSecretKey.value.length == 32) {
+            let oldKey = moeApp.config.get(imageCosSecretKey.id);
+            if (oldKey != imageCosSecretKey.value) {
+                moeApp.config.set(imageCosSecretKey.id, imageCosSecretKey.value);
+                if (hasCosServer()) {
+                    cosServer.update(imageCosAccessKey.value, imageCosSecretKey.value)
+                    ipcRenderer.send('setting-changed', {key: imageCosSecretKey.id, val: imageCosSecretKey.value});
+                    checkCosBuckets();
+                }
+            }
+        }
+    })
+
+    imageCosSecretKey.addEventListener('focus', () => {
+        imageCosSecretKey.type = 'type';
+    })
+
+    imageCosBucket.addEventListener('change', function (e) {
+        moeApp.config.set(imageCosBucket.id, imageCosBucket.value);
+        imageCosBaseWeb.value = imageCosBucket.value.replace('|','.cos.') + '.myqcloud.com' + '/';
+        imageCosBaseWeb.title = imageCosBaseWeb.value;
+        moeApp.config.set(imageCosBaseWeb.id, imageCosBaseWeb.value);
+        ipcRenderer.send('setting-changed', {key: imageCosBucket.id, val: imageCosBucket.value});
+        imageCosBaseWeb.dispatchEvent(new Event('change'));
+    })
+
+
+    function protocolChange(type) {
+        if ((!type && imageCosBaseWebProtocol.value == 'http://') ||  type == 'https://') {
+            imageCosBaseWebProtocol.style.width = '53px';
+            imageCosBaseWeb.style.width = 'calc(100% - 57px)';
+            imageCosBaseWebProtocol.value = 'https://';
+        } else {
+            imageCosBaseWebProtocol.style.width = '47px';
+            imageCosBaseWeb.style.width = 'calc(100% - 51px)';
+            imageCosBaseWebProtocol.value = 'http://';
+        }
+    }
+
+    imageCosBaseWebProtocol.value = moeApp.config.get(imageCosBaseWebProtocol.id);
+    protocolChange(imageCosBaseWebProtocol.value);
+    imageCosBaseWebProtocol.addEventListener('click', () => {
+        protocolChange();
+        imageCosBaseWeb.dispatchEvent(new Event('change'));
+    })
+
+    imageCosBaseWeb.addEventListener('change', function (e) {
+        let protocol = moeApp.config.get(imageCosBaseWebProtocol.id)
+        let oldURL = moeApp.config.get(imageCosBaseWeb.id);
+        let value = {
+            oldURL: (oldURL ? protocol + oldURL : ''),
+            newURL: (imageCosBaseWeb.value ? imageCosBaseWebProtocol.value + imageCosBaseWeb.value : '')
+        };
+        moeApp.config.set(imageCosBaseWebProtocol.id, imageCosBaseWebProtocol.value);
+        moeApp.config.set(imageCosBaseWeb.id, imageCosBaseWeb.value);
+        ipcRenderer.send('setting-changed', {key: imageCosBaseWeb.id, val: value});
+    })
+
     let type = moeApp.config.get(imageType.id);
-    if (type == 'qiniu') {
+    if (type == 'qiniu' || type == 'cos') {
         imageType.value = type;
         let event = new Event('change');
         imageType.dispatchEvent(event);

+ 27 - 0
views/settings/settings.html

@@ -304,6 +304,7 @@
                                     <select class="settings-item-image" id="image-web-type">
                                         <option value="sm" class="l10n">WebSM</option>
                                         <option value="qiniu" class="l10n">QiNiu</option>
+                                        <option value="cos" class="l10n">WebCOS</option>
                                     </select>
                                 </td>
                             </tr>
@@ -334,6 +335,32 @@
                                     </select>
                                 </td>
                             </tr>
+                            <tr data-type="image-cos" style="display:none">
+                                <td width="30%" class="l10n">AccessKey</td>
+                                <td width="70%">
+                                    <input class="settings-item-image" type="password" id="image-cos-accessKey"/>
+                                </td>
+                            </tr>
+                            <tr data-type="image-cos" style="display:none">
+                                <td width="30%" class="l10n">SecretKey</td>
+                                <td width="70%">
+                                    <input class="settings-item-image" type="password" id="image-cos-secretKey"/>
+                                </td>
+                            </tr>
+                            <tr data-type="image-cos" style="display:none">
+                                <td width="30%" class="l10n">Bucket</td>
+                                <td width="70%">
+                                    <select class="settings-item-image" id="image-cos-bucket">
+                                    </select>
+                                </td>
+                            </tr>
+                            <tr data-type="image-cos" style="display:none">
+                                <td width="30%" class="l10n">BaseWeb</td>
+                                <td width="70%">
+                                    <input class="settings-item-image" id="image-cos-url-protocol" readonly style="transition: width .5s;  width:53px" />
+                                    <input class="settings-item-image" id="image-cos-url" readonly style="transition: width .5s;width: calc(100% - 54px);" />
+                                </td>
+                            </tr>
                         </table>
                     </div>
                 </div>