@@ -257,9 +261,16 @@ const dataVerificationCtrl = {
             e.stopPropagation();
         });
         $(document).off("click.dropdownListItem").on("click.dropdownListItem", "#luckysheet-dataVerification-dropdown-List .dropdown-List-item", function(e) {
-            $("#luckysheet-dataVerification-dropdown-List").hide();
-            
+            var $item = $(this);
             let value = e.target.innerText;
+            if ($item.hasClass('multi')) {
+                $item.toggleClass('checked');
+                value = $.map($("#luckysheet-dataVerification-dropdown-List").children().filter('.checked'), function(el) {
+                    return el.innerText;
+                }).join(',');
+            } else {
+                $("#luckysheet-dataVerification-dropdown-List").hide();
+            }
             let last = Store.luckysheet_select_save[Store.luckysheet_select_save.length - 1];
             let rowIndex = last.row_focus;
             let colIndex = last.column_focus;
@@ -437,6 +448,8 @@ const dataVerificationCtrl = {
                 }
 
                 $("#luckysheet-dataVerification-dialog .show-box-item-dropdown .data-verification-value1").val(value1);
+                
+                $('#luckysheet-dataVerification-dialog #data-verification-multi').prop('checked', item.type2 ? true : false);
             }
             else if(value == 'checkbox'){
                 $("#luckysheet-dataVerification-dialog .show-box .show-box-item-checkbox").show();
@@ -635,6 +648,7 @@ const dataVerificationCtrl = {
                     tooltip.info('
', dvText.tooltipInfo1);
                     return;
                 }
+                type2 = $("#luckysheet-dataVerification-dialog #data-verification-multi").is(':checked');
             }
             else if(type == 'checkbox'){
                 value1 = $("#luckysheet-dataVerification-dialog .show-box-item-checkbox .data-verification-value1").val().trim();
@@ -839,6 +853,7 @@ const dataVerificationCtrl = {
         if(item.type == 'dropdown'){
             $("#luckysheet-dataVerification-dialog .show-box .show-box-item-dropdown").show();
             $("#luckysheet-dataVerification-dialog .show-box-item-dropdown .data-verification-value1").val(item.value1);
+            $('#luckysheet-dataVerification-dialog #data-verification-multi').prop('checked', item.type2 ? true : false);
         }
         else if(item.type == 'checkbox'){
             $("#luckysheet-dataVerification-dialog .show-box .show-box-item-checkbox").show();
@@ -1270,6 +1285,13 @@ const dataVerificationCtrl = {
         if(type == 'dropdown'){
             let list = _this.getDropdownList(value1);
 
+            // 多选的情况 检查每个都在下拉列表中
+            if(type2 && cellValue){
+                return cellValue.split(',').every(function (i) {
+                    return list.indexOf(i) !== -1;
+                });
+            }
+
             let result = false;
 
             for(let i = 0; i < list.length; i++){
@@ -1464,9 +1486,19 @@ const dataVerificationCtrl = {
         let list = _this.getDropdownList(item.value1);
 
         let optionHtml = '';
-        list.forEach(i => {
-            optionHtml += `
${i}
`;
-        })
+        if (item.type === 'dropdown' && item.type2) {
+            // 下拉多选的情况下 将已经选择的标出来
+            let cellValue = getcellvalue(rowIndex, colIndex, null);
+            let valueArr = isRealNull(cellValue) ? [] : cellValue.split(',');
+            list.forEach(i => {
+                let checked = valueArr.indexOf(i) !== -1;
+                optionHtml += `
${i}
`;
+            });
+        } else {
+            list.forEach(i => {
+                optionHtml += `
${i}
`;
+            });
+        }
 
         $("#luckysheet-dataVerification-dropdown-List")
         .html(optionHtml)
diff --git a/src/css/luckysheet-core.css b/src/css/luckysheet-core.css
index 571c41f..ffec0c4 100644
--- a/src/css/luckysheet-core.css
+++ b/src/css/luckysheet-core.css
@@ -7200,6 +7200,14 @@ fieldset[disabled] .btn-danger.focus {
     cursor: pointer;
     color: #6598F3;
 }
+#luckysheet-dataVerification-dialog .box-item .multi {
+    margin-top: 10px;
+    line-height: 30px;
+    font-size: 12px;
+}
+#luckysheet-dataVerification-dialog .box-item .multi input {
+    vertical-align: text-top;
+}
 #luckysheet-dataVerification-dialog .box-item .show-box{
     margin-top: 10px;
 }
@@ -7303,6 +7311,24 @@ fieldset[disabled] .btn-danger.focus {
     box-sizing: border-box;
     cursor: pointer;
 }
+
+#luckysheet-dataVerification-dropdown-List .dropdown-List-item.multi {
+    padding-left: 0;
+}
+#luckysheet-dataVerification-dropdown-List .dropdown-List-item.multi:before{
+    content: "";
+    width: 14px;
+    font-family: "iconfont" !important;
+    font-size: 12px;
+    font-style: normal;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    display: inline-block;
+    margin-right: 2px;
+}
+#luckysheet-dataVerification-dropdown-List .dropdown-List-item.multi.checked:before{
+    content: "\e7c8"; 
+}
 #luckysheet-dataVerification-dropdown-List .dropdown-List-item:hover{
     background-color: #E1E1E1;
 }
diff --git a/src/locale/en.js b/src/locale/en.js
index 9b4f1dd..bc370f3 100644
--- a/src/locale/en.js
+++ b/src/locale/en.js
@@ -9733,6 +9733,7 @@ export default {
         selectCellRange: 'Click to select a cell range',
         selectCellRange2: 'Please select a range of cells',
         verificationCondition: 'Verification condition',
+        allowMultiSelect: "Allow multiple selection",
         dropdown: 'drop-down list',
         checkbox: 'Checkbox',
         number: 'Number',
diff --git a/src/locale/es.js b/src/locale/es.js
index 9e93056..f5466e2 100644
--- a/src/locale/es.js
+++ b/src/locale/es.js
@@ -9716,6 +9716,7 @@ export default {
         selectCellRange: 'Haz clic para seleccionar un rango de celdas',
         selectCellRange2: 'Selecciona un rango de celdas',
         verificationCondition: 'Condición de verificación',
+        allowMultiSelect: "Permitir selección múltiple",
         dropdown: 'lista desplegable',
         checkbox: 'Casilla de verificación',
         number: 'Número',
diff --git a/src/locale/zh.js b/src/locale/zh.js
index 895deb9..c9f21a6 100644
--- a/src/locale/zh.js
+++ b/src/locale/zh.js
@@ -9976,6 +9976,7 @@ export default {
         selectCellRange: '点击选择单元格范围',
         selectCellRange2: '请选择单元格范围',
         verificationCondition: '验证条件',
+        allowMultiSelect: '是否允许多选',
         dropdown: '下拉列表',
         checkbox: '复选框',
         number: '数字',
diff --git a/src/locale/zh_tw.js b/src/locale/zh_tw.js
index 90e394c..9069c0c 100644
--- a/src/locale/zh_tw.js
+++ b/src/locale/zh_tw.js
@@ -9969,6 +9969,7 @@ export default {
         selectCellRange      : '點擊選擇儲存格範圍',
         selectCellRange2     : '請選擇儲存格範圍',
         verificationCondition: '驗證條件',
+        allowMultiSelect     : "是否允許多選",
         dropdown             : '下拉清單',
         checkbox             : '核取方塊',
         number               : '數位',
From d0d5e670ece082d17adcbaf83e46df51b626e391 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 10 Mar 2021 08:24:31 +0000
Subject: [PATCH 03/37] chore(deps): bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)
Signed-off-by: dependabot[bot] 
---
 yarn.lock | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 5b0f7ec..0afaa8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2166,10 +2166,10 @@ bluebird@^3.1.1, bluebird@^3.5.5:
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
-  version "4.11.9"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
-  integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
+  version "4.12.0"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
+  integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
 
 bn.js@^5.0.0, bn.js@^5.1.1:
   version "5.1.3"
@@ -2254,7 +2254,7 @@ braces@^3.0.1, braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-brorand@^1.0.1:
+brorand@^1.0.1, brorand@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@@ -4165,17 +4165,17 @@ electron-to-chromium@^1.3.634:
   integrity sha512-bwl6/U6xb3d3CNufQU9QeO1L32ueouFwW4bWANSwdXR7LVqyLzWjNbynoKNfuC38QFB5Qn7O0l2KLqBkcXnC3Q==
 
 elliptic@^6.5.3:
-  version "6.5.3"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
-  integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
+  version "6.5.4"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
+  integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
   dependencies:
-    bn.js "^4.4.0"
-    brorand "^1.0.1"
+    bn.js "^4.11.9"
+    brorand "^1.1.0"
     hash.js "^1.0.0"
-    hmac-drbg "^1.0.0"
-    inherits "^2.0.1"
-    minimalistic-assert "^1.0.0"
-    minimalistic-crypto-utils "^1.0.0"
+    hmac-drbg "^1.0.1"
+    inherits "^2.0.4"
+    minimalistic-assert "^1.0.1"
+    minimalistic-crypto-utils "^1.0.1"
 
 emoji-regex@^7.0.1:
   version "7.0.3"
@@ -5624,7 +5624,7 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
 
-hmac-drbg@^1.0.0:
+hmac-drbg@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
   integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@@ -7296,7 +7296,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
   integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
 
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+minimalistic-crypto-utils@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
From 4fc4105ee5b325df763428228d7cc6b45a72d22e Mon Sep 17 00:00:00 2001
From: susiyang 
Date: Wed, 10 Mar 2021 18:47:12 +0800
Subject: [PATCH 04/37] fix bug #563
---
 src/controllers/protection.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/controllers/protection.js b/src/controllers/protection.js
index c7783a5..f0b6779 100644
--- a/src/controllers/protection.js
+++ b/src/controllers/protection.js
@@ -922,7 +922,7 @@ export function checkProtectionLocked(r, c, sheetIndex, isOpenAlert=true, isLock
 //cell hidden state
 export function checkProtectionCellHidden(r, c, sheetIndex){
     let sheetFile = sheetmanage.getSheetByIndex(sheetIndex);
-    if(!sheetFile || !sheetFile.data[r] || !sheetFile.data[r][c]){
+    if(!sheetFile || (sheetFile.data && !sheetFile.data[r]) || (sheetFile.data && !sheetFile.data[r][c])){
         return true;
     }
 
From d84c4b692b14b69cc766b46d666ba171e0cd8d1b Mon Sep 17 00:00:00 2001
From: susiyang 
Date: Wed, 10 Mar 2021 21:12:59 +0800
Subject: [PATCH 05/37] fixed bug: #565
---
 src/controllers/select.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/controllers/select.js b/src/controllers/select.js
index 7371b8b..1aacd03 100644
--- a/src/controllers/select.js
+++ b/src/controllers/select.js
@@ -403,10 +403,10 @@ function collaborativeEditBox() {
             if (Object.keys(range).length > 0 ) {
                 let flag_sure_merge = false
                 if(range.v.length > 1) {
-                    flag_sure_merge = Object.keys(range.v[1][0]).length > 0
+                    flag_sure_merge = range.v[1][0] == null || Object.keys(range.v[1][0]).length > 0
                 }
                 if(range.v[0].length > 1) {
-                    flag_sure_merge = Object.keys(range.v[0][1]).length > 0
+                    flag_sure_merge = range.v[0][1] == null || Object.keys(range.v[0][1]).length > 0
                 }
                 if(flag_sure_merge) {
                     // 合并成一个时执行
From d832ed5ede92d8d3a7ecbc5339a8d7b51b1285f8 Mon Sep 17 00:00:00 2001
From: susiyang 
Date: Wed, 10 Mar 2021 21:20:57 +0800
Subject: [PATCH 06/37] fixed bug: #565
---
 src/controllers/select.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/controllers/select.js b/src/controllers/select.js
index 7371b8b..1aacd03 100644
--- a/src/controllers/select.js
+++ b/src/controllers/select.js
@@ -403,10 +403,10 @@ function collaborativeEditBox() {
             if (Object.keys(range).length > 0 ) {
                 let flag_sure_merge = false
                 if(range.v.length > 1) {
-                    flag_sure_merge = Object.keys(range.v[1][0]).length > 0
+                    flag_sure_merge = range.v[1][0] == null || Object.keys(range.v[1][0]).length > 0
                 }
                 if(range.v[0].length > 1) {
-                    flag_sure_merge = Object.keys(range.v[0][1]).length > 0
+                    flag_sure_merge = range.v[0][1] == null || Object.keys(range.v[0][1]).length > 0
                 }
                 if(flag_sure_merge) {
                     // 合并成一个时执行
From 2ccd72d2f333991c50708226e5f9f5a2a096ec16 Mon Sep 17 00:00:00 2001
From: susiyang 
Date: Thu, 11 Mar 2021 11:39:24 +0800
Subject: [PATCH 07/37] =?UTF-8?q?Feature:=20#567=20=E5=8D=8F=E4=BD=9C?=
 =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=92=A9=E5=AD=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
 docs/guide/config.md      | 10 ++++++++++
 docs/zh/guide/config.md   | 12 ++++++++++++
 src/controllers/server.js |  2 ++
 3 files changed, 24 insertions(+)
diff --git a/docs/guide/config.md b/docs/guide/config.md
index f847327..c03224f 100644
--- a/docs/guide/config.md
+++ b/docs/guide/config.md
@@ -1169,6 +1169,16 @@ The hook functions are uniformly configured under ʻoptions.hook`, and configura
     
 ------------
 
+## Cooperative
+
+### cooperativeMessage
+
+- Type:Function
+- Default:null
+- Usage:Receive the cooperation message, secondary development. Expanding cooperative message instruction set
+- Params:
+	- {Object} : Receives the entire collaboration message body object sent by the server
+
 ## Image
 
 ### imageInsertBefore
diff --git a/docs/zh/guide/config.md b/docs/zh/guide/config.md
index d48cbed..7afef3f 100644
--- a/docs/zh/guide/config.md
+++ b/docs/zh/guide/config.md
@@ -1444,6 +1444,18 @@ Luckysheet开放了更细致的自定义配置选项,分别有
 	- {Number} [canvasHeight]: 滚动容器的高度
     
 ------------
+
+
+## 协作消息
+
+### cooperativeMessage
+
+- 类型:Function
+- 默认值:null
+- 作用:接受协作消息,二次开发。拓展协作消息指令集
+- 参数:
+	- {Object} : 收到服务器发送的整个协作消息体对象
+  
 ## 图片
 
 ### imageInsertBefore
diff --git a/src/controllers/server.js b/src/controllers/server.js
index 8bdfbc3..4d228fa 100644
--- a/src/controllers/server.js
+++ b/src/controllers/server.js
@@ -20,6 +20,7 @@ import dayjs from "dayjs";
 import json from '../global/json';
 import luckysheetConfigsetting from './luckysheetConfigsetting';
 import {customImageUpdate} from './imageUpdateCtrl';
+import method from '../global/method';
 
 const server = {
     gridKey: null,
@@ -199,6 +200,7 @@ const server = {
 	        _this.websocket.onmessage = function(result){
 				Store.result = result
 				let data = new Function("return " + result.data)();
+        method.createHookFunction('cooperativeMessage', data)
 				console.info(data);
 				let type = data.type;
 				let {message,id} = data;
From 7f4e14d93c38ea8fcabd5a4cfddbe6d4cb38003d Mon Sep 17 00:00:00 2001
From: susiyang 
Date: Sat, 20 Mar 2021 00:48:07 +0800
Subject: [PATCH 08/37] =?UTF-8?q?fixed:=20#573=20=E5=A4=8D=E5=88=B6?=
 =?UTF-8?q?=E5=A4=9A=E8=A1=8C=E6=96=87=E6=9C=AC=EF=BC=88inlineStr=EF=BC=89?=
 =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=8D=95=E5=85=83=E6=A0=BC=E6=97=A0?=
 =?UTF-8?q?=E6=B3=95=E5=A4=8D=E5=88=B6=E5=92=8C=E7=B2=98=E8=B4=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
 src/controllers/handler.js   | 22 ++++++++++++++++++----
 src/controllers/selection.js | 11 +++++++++++
 2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/src/controllers/handler.js b/src/controllers/handler.js
index 61724a7..3185cae 100644
--- a/src/controllers/handler.js
+++ b/src/controllers/handler.js
@@ -5329,7 +5329,7 @@ export default function luckysheetHandler() {
 
                     for(let c = copy_c1; c <= copy_c2; c++){
                         let cell = d[r][c];
-
+                        let isInlineStr = false
                         if(cell != null && cell.mc != null && cell.mc.rs == null){
                             continue;
                         }
@@ -5347,13 +5347,27 @@ export default function luckysheetHandler() {
                             v = "";
                         }
 
+                        
+                        if(v == null && d[r][c] && d[r][c].ct && d[r][c].ct.t == 'inlineStr') {
+                          v = d[r][c].ct.s.map(val=>val.v).join('');
+                          isInlineStr = true;
+                        }
                         if(v == null){
-                            v = "";
+                          v = "";
                         }
-
-                        if(cpDataArr[r - copy_r1][c - copy_c1] != v){
+                        if(isInlineStr){
+                          const cpData = $(cpDataArr[r - copy_r1][c - copy_c1]).text().replace(/\s|\n/g,' ')
+                          const storeValue = v.replace(/\n/g,'').replace(/\s/g,' ')
+                          if(cpData != storeValue){
+                            isEqual = false;
+                            break;
+                          }
+                        }
+                        else{
+                          if(cpDataArr[r - copy_r1][c - copy_c1] != v){
                             isEqual = false;
                             break;
+                          }
                         }
                     }
                 }
diff --git a/src/controllers/selection.js b/src/controllers/selection.js
index d0045a8..7fbabf7 100644
--- a/src/controllers/selection.js
+++ b/src/controllers/selection.js
@@ -438,6 +438,17 @@ const selection = {
                     if(c_value == null){
                         c_value = getcellvalue(r, c, d);
                     }
+                    if(c_value == null && d[r][c] && d[r][c].ct && d[r][c].ct.t == 'inlineStr') {
+                      c_value = d[r][c].ct.s.map(val=>{
+                        const font = $('')
+                        val.fs && font.css('font-size',val.fs)
+                        val.bl && font.css('font-weight',val.border)
+                        val.it && font.css('font-style',val.italic)
+                        val.cl==1 && font.css('text-decoration','underline')
+                        font.text(val.v)
+                        return font[0].outerHTML
+                      }).join('');
+                    }
 
                     if(c_value == null){
                         c_value = "";
From 627183f571976335433798667ba26657de0d8dbc Mon Sep 17 00:00:00 2001
From: zhangsimu 
Date: Thu, 25 Mar 2021 08:29:53 +0800
Subject: [PATCH 09/37] feat(controllers/rowcolumnoperation.j): youjian add he
 delete hook hanshu,api charu
xiangxiacharu he xiangyoucharu
---
 src/controllers/rowColumnOperation.js | 20 ++++++-
 src/global/api.js                     | 79 ++++++++++++++++++++++++++-
 2 files changed, 95 insertions(+), 4 deletions(-)
diff --git a/src/controllers/rowColumnOperation.js b/src/controllers/rowColumnOperation.js
index bd5768d..0c5d654 100644
--- a/src/controllers/rowColumnOperation.js
+++ b/src/controllers/rowColumnOperation.js
@@ -4,6 +4,7 @@ import luckysheetPostil from './postil';
 import imageCtrl from './imageCtrl';
 import menuButton from './menuButton';
 import server from './server';
+import method from '../global/method';
 import { selectHightlightShow, luckysheet_count_show,selectHelpboxFill } from './select';
 import { 
     getObjType, 
@@ -1103,7 +1104,10 @@ export function rowColumnOperationInitial(){
         }
 
         let st_index = Store.luckysheet_select_save[0][Store.luckysheetRightHeadClickIs][0];
-        luckysheetextendtable(Store.luckysheetRightHeadClickIs, st_index, value, "lefttop");
+		if(!method.createHookFunction("rowInsertBefore",  st_index, value, "lefttop")){ 
+			return; 
+		}
+		luckysheetextendtable(Store.luckysheetRightHeadClickIs, st_index, value, "lefttop");
     });
 
 
@@ -1117,6 +1121,9 @@ export function rowColumnOperationInitial(){
         }
 
         let st_index = Store.luckysheet_select_save[0].row[0];
+		if(!method.createHookFunction("rowInsertBefore",  st_index, 1, "lefttop")){ 
+			return; 
+		}
         luckysheetextendtable('row', st_index, 1, "lefttop");
 
 
@@ -1307,6 +1314,9 @@ export function rowColumnOperationInitial(){
         }
 
         let st_index = Store.luckysheet_select_save[0][Store.luckysheetRightHeadClickIs][1];
+		if(!method.createHookFunction("rowInsertBefore",  st_index, value, "rightbottom")){
+			return; 
+		}
         luckysheetextendtable(Store.luckysheetRightHeadClickIs, st_index, value, "rightbottom");
     });
 
@@ -1453,7 +1463,10 @@ export function rowColumnOperationInitial(){
 
         let st_index = Store.luckysheet_select_save[0][Store.luckysheetRightHeadClickIs][0], 
             ed_index = Store.luckysheet_select_save[0][Store.luckysheetRightHeadClickIs][1];
-        luckysheetdeletetable(Store.luckysheetRightHeadClickIs, st_index, ed_index);
+        if(!method.createHookFunction("rowDeleteBefore", st_index, ed_index)){
+        	return; 
+        }
+		luckysheetdeletetable(Store.luckysheetRightHeadClickIs, st_index, ed_index);
     });
     $("#luckysheet-delRows").click(function (event) {
         $("#luckysheet-rightclick-menu").hide();
@@ -1483,6 +1496,9 @@ export function rowColumnOperationInitial(){
 
         let st_index = Store.luckysheet_select_save[0].row[0], 
             ed_index = Store.luckysheet_select_save[0].row[1];
+		if(!method.createHookFunction("rowDeleteBefore", st_index, ed_index)){
+			return; 
+		}
         luckysheetdeletetable('row', st_index, ed_index);
     })
     $("#luckysheet-delCols").click(function (event) {
diff --git a/src/global/api.js b/src/global/api.js
index 3d586b6..e9c9c39 100644
--- a/src/global/api.js
+++ b/src/global/api.js
@@ -1082,7 +1082,62 @@ export function insertRowOrColumn(type, index = 0, options = {}) {
         success();
     }
 }
+/**
+ * 在第index行或列的位置,插入number行或列
+ * @param {String} type 插入行或列 row-行  column-列
+ * @param {Number} index 在第几行插入空白行,从0开始
+ * @param {Object} options 可选参数
+ * @param {Number} options.number 插入的空白行数;默认为 1
+ * @param {Number} options.order 工作表索引;默认值为当前工作表索引
+ * @param {Function} options.success 操作结束的回调函数
+ */
+export function insertRowBottomOrColumnRight(type, index = 0, options = {}) {
+    if(!isRealNum(index)){
+        return tooltip.info('The index parameter is invalid.', '');
+    }
+
+    let curSheetOrder = getSheetIndex(Store.currentSheetIndex);
+    let {
+        number = 1,
+        order = curSheetOrder,
+        success
+    } = {...options}
 
+    let _locale = locale();
+    let locale_info = _locale.info;
+    if (!isRealNum(number)) {
+        if(isEditMode()){
+            alert(locale_info.tipInputNumber);
+        } else{
+            tooltip.info(locale_info.tipInputNumber, "");
+        }
+        return;
+    }
+
+    number = parseInt(number);
+    if (number < 1 || number > 100) {
+        if(isEditMode()){
+            alert(locale_info.tipInputNumberLimit);
+        } else{
+            tooltip.info(locale_info.tipInputNumberLimit, "");
+        }
+        return;
+    }
+
+    // 默认在行上方增加行,列左侧增加列
+    let sheetIndex;
+    if(order){
+        if(Store.luckysheetfile[order]){
+            sheetIndex = Store.luckysheetfile[order].index;
+        }
+    }
+
+    luckysheetextendtable(type, index, number, "rightbottom", sheetIndex);
+
+    if (success && typeof success === 'function') {
+        success();
+    }
+}
 /**
  * 在第row行的位置,插入number行空白行
  * @param {Number} row 在第几行插入空白行,从0开始
@@ -1094,7 +1149,17 @@ export function insertRowOrColumn(type, index = 0, options = {}) {
 export function insertRow(row = 0, options = {}) {
     insertRowOrColumn('row', row, options)
 }
-
+/**
+ * 在第row行的位置,插入number行空白行
+ * @param {Number} row 在第几行插入空白行,从0开始
+ * @param {Object} options 可选参数
+ * @param {Number} options.number 插入的空白行数;默认为 1
+ * @param {Number} options.order 工作表索引;默认值为当前工作表索引
+ * @param {Function} options.success 操作结束的回调函数
+ */
+export function insertRowBottom(row = 0, options = {}) {
+    insertRowBottomOrColumnRight('row', row, options)
+}
 /**
  * 在第column列的位置,插入number列空白列
  * @param {Number} column 在第几列插入空白列,从0开始
@@ -1106,7 +1171,17 @@ export function insertRow(row = 0, options = {}) {
 export function insertColumn(column = 0, options = {}) {
     insertRowOrColumn('column', column, options)
 }
-
+/**
+ * 在第column列的位置,插入number列空白列
+ * @param {Number} column 在第几列插入空白列,从0开始
+ * @param {Object} options 可选参数
+ * @param {Number} options.number 插入的空白列数;默认为 1
+ * @param {Number} options.order 工作表索引;默认值为当前工作表索引
+ * @param {Function} options.success 操作结束的回调函数
+ */
+export function insertColumnRight(column = 0, options = {}) {
+    insertRowBottomOrColumnRight('column', column, options)
+}
 /**
  * 删除指定的行或列。删除行列之后,行列的序号并不会变化,下面的行(右侧的列)会补充到上(左)面,注意观察数据是否被正确删除即可。
  * @param {String} type 删除行或列 row-行  column-列
From 8b90e1d093d48725e38d80d40e807d26712c42ea Mon Sep 17 00:00:00 2001
From: btea <2356281422@qq.com>
Date: Tue, 30 Mar 2021 11:43:56 +0800
Subject: [PATCH 10/37] edit: filter xss
---
 src/controllers/updateCell.js |  2 +-
 src/global/formula.js         | 12 ++++++++----
 2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/controllers/updateCell.js b/src/controllers/updateCell.js
index 26e7bfd..d120db4 100644
--- a/src/controllers/updateCell.js
+++ b/src/controllers/updateCell.js
@@ -213,7 +213,7 @@ export function luckysheetupdateCell(row_index1, col_index1, d, cover, isnotfocu
     if((value == null || value.toString() == "") && !cover){
         value = "
";
     }
-    
+    value = formula.xssDeal(value);
     if(!checkProtectionCellHidden(row_index, col_index, Store.currentSheetIndex) && value.length>0 && value.substr(0, 63)=='='){
         $("#luckysheet-rich-text-editor").html("");
     }
diff --git a/src/global/formula.js b/src/global/formula.js
index 4d2c5ea..f38efe2 100644
--- a/src/global/formula.js
+++ b/src/global/formula.js
@@ -321,6 +321,10 @@ const luckysheetformula = {
             sheetmanage.changeSheetExec(_this.rangetosheet);
         }
     },
+    xssDeal: function(str) {
+        if (typeof str !== 'string') return str;
+        return str.replace(/