Forráskód Böngészése

Merge branch 'dev_v1.1.4' into dev_1.1.5

zhangwei 2 éve
szülő
commit
f2f9d0ca58
45 módosított fájl, 2711 hozzáadás és 217 törlés
  1. BIN
      src/assets/img/icon-close.png
  2. BIN
      src/assets/img/icon-default-app-logo.png
  3. BIN
      src/assets/img/icon-fixed-gray.png
  4. BIN
      src/assets/img/icon-fixed.png
  5. BIN
      src/assets/img/icon-full.png
  6. BIN
      src/assets/img/icon-loading-gray.png
  7. BIN
      src/assets/img/icon-page-fail.png
  8. BIN
      src/assets/img/icon-tool-box-01.png
  9. BIN
      src/assets/img/img-tool-box-preview-after.png
  10. BIN
      src/assets/img/img-tool-box-preview-before.png
  11. 3 0
      src/assets/svg/icon-tool-app-history.svg
  12. 5 0
      src/assets/svg/icon-tool-box-02.svg
  13. 3 0
      src/assets/svg/icon-tool-box-guide-arrow.svg
  14. 3 0
      src/assets/svg/icon-tool-box-search-arrow.svg
  15. 6 0
      src/assets/svg/img-default-site-cover.svg
  16. 7 0
      src/assets/svg/img-select-giveaway.svg
  17. 6 0
      src/assets/svg/img-select-tool-box.svg
  18. 28 8
      src/entry/background.js
  19. 13 8
      src/entry/content.js
  20. 1 1
      src/http/configAPI.js
  21. 17 0
      src/http/toolBoxApi.js
  22. 5 0
      src/iframe/test.js
  23. 9 0
      src/iframe/tool-box-guide.js
  24. 62 46
      src/logic/background/twitter.js
  25. 53 1
      src/logic/content/ParseCard.js
  26. 26 0
      src/logic/content/ToolBox.js
  27. 115 73
      src/logic/content/twitter.js
  28. 18 2
      src/manifest.json
  29. 9 4
      src/rules/rules_1.json
  30. 36 5
      src/uilts/chromeExtension.js
  31. 12 0
      src/uilts/event.js
  32. 13 0
      src/uilts/help.js
  33. 1 1
      src/view/components/global-tip.vue
  34. 150 0
      src/view/content/tool-box/full.vue
  35. 436 0
      src/view/content/tool-box/index.vue
  36. 141 0
      src/view/iframe/publish/components/select-publish-content.vue
  37. 113 27
      src/view/iframe/publish/give-dialog.vue
  38. 37 10
      src/view/iframe/publish/publish.vue
  39. 416 0
      src/view/iframe/publish/tool-box/child/editor.vue
  40. 172 0
      src/view/iframe/publish/tool-box/child/guide.vue
  41. 391 0
      src/view/iframe/publish/tool-box/child/preview.vue
  42. 70 0
      src/view/iframe/publish/tool-box/index.vue
  43. 65 27
      src/view/iframe/tab-group/tab-group.vue
  44. 265 0
      src/view/iframe/test/index.vue
  45. 4 4
      vue.config.js

BIN
src/assets/img/icon-close.png


BIN
src/assets/img/icon-default-app-logo.png


BIN
src/assets/img/icon-fixed-gray.png


BIN
src/assets/img/icon-fixed.png


BIN
src/assets/img/icon-full.png


BIN
src/assets/img/icon-loading-gray.png


BIN
src/assets/img/icon-page-fail.png


BIN
src/assets/img/icon-tool-box-01.png


BIN
src/assets/img/img-tool-box-preview-after.png


BIN
src/assets/img/img-tool-box-preview-before.png


+ 3 - 0
src/assets/svg/icon-tool-app-history.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.9996 20.6663C16.7862 20.6663 20.6663 16.7862 20.6663 11.9996C20.6663 7.21301 16.7862 3.33294 11.9996 3.33294C7.21301 3.33294 3.33294 7.21301 3.33294 11.9996C3.33294 16.7862 7.21301 20.6663 11.9996 20.6663ZM11.9996 22.3996C6.25621 22.3996 1.59961 17.743 1.59961 11.9996C1.59961 6.25621 6.25621 1.59961 11.9996 1.59961C17.743 1.59961 22.3996 6.25621 22.3996 11.9996C22.3996 17.743 17.743 22.3996 11.9996 22.3996ZM12.8663 12.5101V7.66368C12.8663 7.19221 12.478 6.79961 11.9996 6.79961C11.5177 6.79961 11.1329 7.18614 11.1329 7.66368V12.8689C11.1322 12.9813 11.1537 13.0929 11.1963 13.197C11.2388 13.3011 11.3015 13.3958 11.3808 13.4755L13.8439 15.9386C13.9242 16.0185 14.0196 16.0817 14.1244 16.1246C14.2293 16.1676 14.3415 16.1894 14.4548 16.1888C14.5681 16.1882 14.6802 16.1653 14.7846 16.1213C14.889 16.0774 14.9837 16.0132 15.0633 15.9325C15.2247 15.7711 15.3159 15.5524 15.317 15.3241C15.3182 15.0958 15.2292 14.8762 15.0693 14.7131L12.8663 12.5101Z" fill="#AFB3B6"/>
+</svg>

+ 5 - 0
src/assets/svg/icon-tool-box-02.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.8737 13.4889L1.59961 8.13477V17.5585C1.59979 17.7925 1.66091 18.0225 1.77696 18.2258C1.89301 18.4291 2.05999 18.5986 2.26147 18.7177L11.1851 23.9991V13.6143C11.0759 13.5873 10.9711 13.5451 10.8737 13.4889Z" fill="#1D9BF0"/>
+<path d="M22.5015 7.99219V17.5588C22.5014 17.7927 22.4405 18.0226 22.3247 18.2259C22.2089 18.4291 22.0422 18.5987 21.841 18.718L12.916 23.9994V13.5243C12.9379 13.5133 12.9595 13.5016 12.9807 13.4892L22.5015 7.99219Z" fill="#24D37F"/>
+<path d="M1.59961 5.93698C1.71483 5.79133 1.85894 5.67109 2.02287 5.58381L11.4587 0.558553C11.6537 0.454705 11.8713 0.400391 12.0923 0.400391C12.3132 0.400391 12.5308 0.454705 12.7258 0.558553L22.1617 5.58381C22.2897 5.65121 22.4043 5.74018 22.5027 5.84128L12.157 11.8142C12.0899 11.8529 12.0267 11.8981 11.9683 11.949C11.9103 11.8981 11.8475 11.853 11.7809 11.8142L1.59961 5.93698Z" fill="#FFBB56"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-tool-box-guide-arrow.svg

@@ -0,0 +1,3 @@
+<svg width="11" height="17" viewBox="0 0 11 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0.972185 1L8.69141 8.5L0.972184 16" stroke="#1D9BF0" stroke-width="2"/>
+</svg>

+ 3 - 0
src/assets/svg/icon-tool-box-search-arrow.svg

@@ -0,0 +1,3 @@
+<svg width="11" height="17" viewBox="0 0 11 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0.968279 1L8.6875 8.5L0.968277 16" stroke="white" stroke-width="2"/>
+</svg>

+ 6 - 0
src/assets/svg/img-default-site-cover.svg

@@ -0,0 +1,6 @@
+<svg width="505" height="265" viewBox="0 0 505 265" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 5C0 2.23857 2.23858 0 5 0H500C502.761 0 505 2.23858 505 5V260C505 262.761 502.761 265 500 265H5C2.23857 265 0 262.761 0 260V5Z" fill="#1D9BF0"/>
+<rect width="505" height="265" fill="white"/>
+<rect opacity="0.2" width="505" height="265" fill="#1D9BF0"/>
+<path d="M254.377 162.959L250.251 162.377C250.251 162.377 247.501 161.795 246.126 161.213C245.341 161.018 244.358 161.213 243.769 161.795L242.787 162.765C237.09 168.393 227.858 168.975 221.965 163.929C215.286 158.302 215.09 148.21 221.179 142.194L236.108 127.445C238.073 125.505 240.43 124.34 242.787 123.564C245.93 122.788 249.269 122.982 252.216 124.146C253.984 124.922 255.752 125.893 257.323 127.445C258.109 128.222 258.698 128.998 259.287 129.968C260.073 131.327 261.841 131.521 262.823 130.356L268.323 124.922C269.109 124.146 269.109 122.982 268.52 122.011C267.734 120.847 266.752 119.877 265.77 118.906C264.395 117.548 262.823 116.19 261.055 115.219C258.305 113.473 255.162 112.308 251.823 111.726C245.733 110.562 239.055 111.532 233.555 114.443C231.394 115.607 229.233 117.16 227.465 118.906L213.125 133.073C202.714 143.359 201.929 160.048 211.947 170.528C222.358 181.783 240.037 181.977 250.841 171.304L255.752 166.452C257.127 165.482 256.144 163.153 254.377 162.959ZM290.521 93.2899C279.717 83.3926 262.823 84.1689 252.609 94.4543L248.091 98.7237C246.716 100.082 247.698 102.411 249.466 102.605C252.216 102.799 254.966 103.381 257.716 104.158C258.502 104.352 259.484 104.158 260.073 103.575L261.055 102.605C266.752 96.9772 275.984 96.395 281.877 101.441C288.556 107.069 288.753 117.16 282.663 123.176L267.734 137.925C265.77 139.865 263.413 141.03 261.055 141.806C257.912 142.582 254.573 142.388 251.626 141.224C249.859 140.448 248.091 139.477 246.519 137.925C245.733 137.149 245.144 136.372 244.555 135.402C243.769 134.043 242.001 133.849 241.019 135.014L235.519 140.448C234.733 141.224 234.733 142.388 235.322 143.359C236.108 144.523 237.09 145.493 238.073 146.464C239.448 147.822 241.215 149.181 242.787 150.151C245.537 151.897 248.68 153.062 251.823 153.644C257.912 154.808 264.591 153.838 270.091 150.927C272.252 149.763 274.413 148.21 276.181 146.464L291.11 131.715C302.11 121.041 301.717 103.575 290.521 93.2899Z" fill="#1D9BF0"/>
+</svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 7 - 0
src/assets/svg/img-select-giveaway.svg


+ 6 - 0
src/assets/svg/img-select-tool-box.svg

@@ -0,0 +1,6 @@
+<svg width="150" height="150" viewBox="0 0 150 150" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="150" height="150" rx="75" fill="#F9F9F9"/>
+<path d="M71.2466 79.9644L40.333 62.1172V93.5296C40.3336 94.3098 40.5374 95.0764 40.9242 95.7539C41.311 96.4315 41.8676 96.9966 42.5392 97.3938L72.2846 114.998V80.3823C71.9207 80.2923 71.5713 80.1517 71.2466 79.9644Z" fill="#1D9BF0"/>
+<path d="M110.006 61.6406V93.5293C110.006 94.309 109.803 95.0754 109.417 95.7529C109.031 96.4304 108.475 96.9958 107.805 97.3935L78.0547 114.998V80.081C78.1277 80.0442 78.1997 80.0052 78.2704 79.9641L110.006 61.6406Z" fill="#24D37F"/>
+<path d="M40.333 54.7912C40.7171 54.3057 41.1975 53.9049 41.7439 53.614L73.1967 36.8631C73.8468 36.517 74.572 36.3359 75.3085 36.3359C76.045 36.3359 76.7703 36.517 77.4204 36.8631L108.873 53.614C109.3 53.8387 109.682 54.1352 110.01 54.4722L75.5242 74.3818C75.3005 74.511 75.0899 74.6615 74.8951 74.8312C74.7018 74.6617 74.4927 74.5113 74.2706 74.3818L40.333 54.7912Z" fill="#FFBB56"/>
+</svg>

+ 28 - 8
src/entry/background.js

@@ -26,6 +26,9 @@ import {
 } from "@/logic/background/twitter";
 import Report from "@/log-center/log"
 import { PingPong, httpNetWork } from "@/logic/background/help";
+import { commonFetch } from '@/http/fetch'
+import { appVersionCode } from '@/http/configAPI.js'
+import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
 
 import {
     facebookShareSuccess
@@ -88,6 +91,14 @@ chrome.tabs.onActivated.addListener(function (activeInfo) {
 })
 function onInstalledMethod() {
     try {
+        // 版本更新判断
+        getChromeStorage('baseInfo', (info) => {
+            if (!info || !info.appVersionCode || appVersionCode != info.appVersionCode) {
+                setChromeStorage({ baseInfo: JSON.stringify({ appVersionCode }) }, () => {
+                    chrome.runtime.reload()
+                })
+            }
+        })
         onInstalledCreateTab()
         onInstalledMid()
         onInstalledUserSet()
@@ -112,7 +123,6 @@ function onInstalledMethod() {
             });
         }, 5000);
     } catch (error) {
-        // 上报错误信息
         Report.reportLog({
             objectType: Report.objectType.background_function_catch,
             funcName: 'onInstalledMethod',
@@ -146,18 +156,12 @@ function onMessageMethod(req, sender, sendResponse) {
                     break
                 case 'CONTENT_SEND_CODE':
                     twitterPinLoginCode(sender, req.code);
+                    break;
                 case 'CONTENT_TWITTER_LOGIN':
                     if (req.data) {
                         twitterPinLoginToken()
                     }
                     break
-                case 'CONTENT_TWITTER_SHORT_LINK':
-                    req.arr_url.forEach(item => {
-                        if (item) {
-                            twitterShortUrl(sender, item)
-                        }
-                    });
-                    break
                 case "CONTENT_SEND_DISCORD_AUTH_CODE":
                     discordLoginCode(req, sender);
                     break
@@ -185,6 +189,22 @@ function onMessageMethod(req, sender, sendResponse) {
                 case 'CONTENT_HTTP_NET_WORK':
                     httpNetWork(req.funcName, req.data, sender)
                     break
+                case 'HTTP_CONTENT_TO_BACK':
+                    commonFetch(req.data)
+                    .then((response) => {
+                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: response, callback_id: req.callback_id });
+                    })
+                    .catch(() => {
+                        chrome.tabs.sendMessage(sender.tab.id, { actionType: 'HTTP_BACK_TO_CONTENT', data: null, callback_id: req.callback_id });
+                    })
+                    break
+                case 'CONTENT_TWITTER_SHORT_LINK':
+                    req.arr_url.forEach(item => {
+                        if (item) {
+                            twitterShortUrl(sender, item)
+                        }
+                    });
+                    break
             }
         }
     } catch (error) {

+ 13 - 8
src/entry/content.js

@@ -41,7 +41,8 @@ import {
     showPublishDialog
 } from "@/logic/content/twitter.js";
 
-import { 
+import { httpBackToContentCallBack } from '@/uilts/chromeExtension.js'
+import {
     hideNFTGroupList,
     setNFTGroupContent,
     setJoinedGroupIframeStyle
@@ -58,9 +59,9 @@ chrome.storage.onChanged.addListener(changes => {
 window.onload = () => {
     init();
     initFacebookContent();
-    chrome.runtime.sendMessage({ 
-        actionType: "CONTENT_WINDOW_LOADED_SET_POPUP_PAGE", 
-        data: { } 
+    chrome.runtime.sendMessage({
+        actionType: "CONTENT_WINDOW_LOADED_SET_POPUP_PAGE",
+        data: {}
     }, () => { });
 };
 
@@ -134,8 +135,8 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             twitterPublishHandler(req.publishRes);
             break;
         case 'IFRAME_TWITTER_SHOW_POPUP_PAGE':
-            let {from = '' ,showJoinGroupFinish} = req.data || {};
-            showPopupPage({path: '/NFT', from,showJoinGroupFinish }); 
+            let { from = '', showJoinGroupFinish } = req.data || {};
+            showPopupPage({ path: '/NFT', from, showJoinGroupFinish });
             break
         case "IFRAME_TWITTER_SHOW_BUY_NFT":
             showBuyNFT(req.data)
@@ -147,7 +148,7 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             setPopupConfByPopupPage();
             break
         case 'IFREME_TAB_GROUP_SET_IFRAME_HEIGHT':
-            console.log('IFREME_TAB_GROUP_SET_IFRAME_HEIGHT',req)
+            console.log('IFREME_TAB_GROUP_SET_IFRAME_HEIGHT', req)
             setTabGroupIframeStyle(req.data);
             break
         case 'IFREME_TAB_GROUP_CONTENT_GET_NAV_TOP':
@@ -183,10 +184,14 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
             break
         case 'BACK_NFT_PUBLISH_DONE':
             refreshTabGroup()
-            break 
+            break
         case 'IFRAME_API_GET_TWEET_USER_INFO_REQ':
             TwitterApiUserByScreenName(req.data)
             break;
+        // 回掉参数
+        case 'HTTP_BACK_TO_CONTENT':
+            httpBackToContentCallBack(req)
+            break
         case 'BG_SHOW_DENET_PUBLISH_DIALOG':
             showPublishDialog();
             break;

+ 1 - 1
src/http/configAPI.js

@@ -1,4 +1,4 @@
-export const appVersionCode = 13
+export const appVersionCode = 14
 
 const api = {
 	production: 'https://api.denetme.net',

+ 17 - 0
src/http/toolBoxApi.js

@@ -0,0 +1,17 @@
+import { service } from "./request";
+
+export function convertUrl(params) {
+    return service({
+        url: `/post/editor/convertUrl`,
+        method: 'post',
+        data: params
+    })
+}
+
+export function getAllPostEditorAppData(params) {
+    return service({
+        url: `/post/editor/getAllPostEditorAppData`,
+        method: 'post',
+        data: params
+    })
+}

+ 5 - 0
src/iframe/test.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from '@/view/iframe/test/index.vue'
+
+const app = createApp(App);
+app.mount('#app');

+ 9 - 0
src/iframe/tool-box-guide.js

@@ -0,0 +1,9 @@
+import { createApp } from 'vue'
+import App from '@/view/iframe/publish/tool-box/child/guide.vue'
+
+const app = createApp(App);
+app.mount('#app');
+
+window.onload= () => {
+    document.title = 'DeNet'
+}

+ 62 - 46
src/logic/background/twitter.js

@@ -248,63 +248,79 @@ function sendActivetabMessage(message = {}) {
 /**
  * 安装后打开新标签页
  */
+
 export function onInstalledCreateTab() {
+    // 落地页参数
     let cookiesParams = {
-        name: 'pickup_info',
+        name: 'jump_info',
         url: pageUrl
     }
     getChromeCookie(cookiesParams, (res) => {
-        let { postNickName, srcContentId } = res;
-        if (res && postNickName && srcContentId) {
-            let url = `https://twitter.com/${postNickName}/status/${srcContentId}`
-            chrome.tabs.create({
-                url
-            });
-            removeChromeCookie(cookiesParams)
-        } else {
-            let nftParams = {
-                name: 'nft_info',
-                url: pageUrl
-            }
-            getChromeCookie(nftParams, (res) => {
-                let { twitterAccount, nftProjectId } = res;
-                if (res && twitterAccount && nftProjectId) {
-                    let url = `https://twitter.com/${twitterAccount}`
+        // jump_info
+        if (!res || !res.jump_type) {
+            return
+        }
+        switch (String(res.jump_type)) {
+            // 普通红包
+            case 'red_packet':
+                if (res && res.postNickName && res.srcContentId) {
+                    let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
                     chrome.tabs.create({
                         url
                     });
-                    removeChromeCookie(nftParams)
-                } else {
-                    let nftGroupParams = {
-                        name: 'nft_group_info',
-                        url: pageUrl
-                    }
-                    getChromeCookie(nftGroupParams, (res) => {
-                        let { twitterAccount } = res;
-                        if (res && twitterAccount) {
-                            // setChromeStorage({ groupTabData: JSON.stringify({
-                            //     deTabVal: 'deGroupTab'
-                            // })})
-                            chrome.storage.local.set({
-                                groupTabData: JSON.stringify({
-                                    deTabVal: 'deGroupTab'
-                                })
-                            }, (res) => {
-                                let url = `https://twitter.com/${twitterAccount}`
-                                chrome.tabs.create({
-                                    url
-                                });
-                            })
-                            removeChromeCookie(nftGroupParams)
-                        } else {
-                            chrome.tabs.create({
-                                url: "https://twitter.com",
-                            });
-                        }
+                }
+                break
+            // 抽奖红包
+            case 'luck_draw':
+                if (res && res.postNickName && res.srcContentId) {
+                    let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
+                    chrome.tabs.create({
+                        url
+                    });
+                }
+                break
+            // NFT
+            case 'ntf_info':
+                if (res && res.twitterAccount && res.nftProjectId) {
+                    let url = `https://twitter.com/${res.twitterAccount}`
+                    chrome.tabs.create({
+                        url
+                    });
+                }
+                break
+            // NFT 组
+            case 'nft_group_info':
+                if (res && res.twitterAccount) {
+                    // setChromeStorage({ groupTabData: JSON.stringify({
+                    //     deTabVal: 'deGroupTab'
+                    // })})
+                    chrome.storage.local.set({
+                        groupTabData: JSON.stringify({
+                            deTabVal: 'deGroupTab'
+                        })
+                    }, (res) => {
+                        let url = `https://twitter.com/${res.twitterAccount}`
+                        chrome.tabs.create({
+                            url
+                        });
                     })
                 }
-            })
+                break
+            // toolbox
+            case 'tool_box':
+                if (res && res.postNickName && res.srcContentId) {
+                    let url = `https://twitter.com/${res.postNickName}/status/${res.srcContentId}`
+                    chrome.tabs.create({
+                        url
+                    });
+                }
+                break
+            default:
+                chrome.tabs.create({
+                    url: "https://twitter.com",
+                });
         }
+        removeChromeCookie(cookiesParams)
     })
 }
 

+ 53 - 1
src/logic/content/ParseCard.js

@@ -1,4 +1,7 @@
 import { getChromeStorage, setChromeStorage } from '@/uilts/chromeExtension.js'
+import ToolBox from '@/view/content/tool-box/index.vue'
+import { createApp } from 'vue'
+
 // 解析卡片类
 // 1.dom匹配
 // 2.找出网页匹配 获取twitterid
@@ -330,6 +333,55 @@ class ParseCard {
         }
         return false
     }
+    addDomView({ tweet_Id, element, parentElement, post_Id }) {
+        const div = document.createElement('div')
+        div.id = 'denet-' + tweet_Id
+        div.style.cssText = `
+        width: 505px;
+        height: 544px;`
+        div.dataset.tweetId = tweet_Id || ''
+        div.dataset.postId = post_Id || ''
+        parentElement.appendChild(div)
+        createApp(element).mount(`#${div.id}`)
+    }
+    replaceDomView({ dom_card, tweet_Id, post_Id, time, short_url, page_type = '' }) {
+        if (!dom_card || !dom_card.parentElement) {
+            return
+        }
+        // class == denet-toolbox
+        let type
+        let dom = dom_card.querySelector('div[aria-labelledby]')
+        if (dom) {
+            type = 'card'
+            for (let i = 0; i < dom.childNodes.length; i++) {
+                if (dom.childNodes[i].dataset && dom.childNodes[i].dataset.testid && dom.childNodes[i].dataset.testid == 'card.wrapper') {
+                    dom.children[i].style.display = 'none'
+                }
+            }
+        } else {
+            type = 'txt'
+            dom = dom_card.querySelector('div[lang][dir=auto]').parentElement
+        }
+
+        dom.style = 'min-height:500px'
+        if (dom) {
+            // debugger mode
+            if (window.location.href.includes('denet_debugger')) {
+                let div = document.createElement('div')
+                div.style.color = 'red'
+                div.innerText = `
+                tweet_Id:${tweet_Id} , 
+                post_Id:${post_Id}
+                获取dom时间:${time}
+                短链接:${short_url}
+                渲染时长:${(new Date().getTime() - time) / 1000}s
+                `
+                dom.parentElement.appendChild(div)
+            }
+            // this.createIframe({ post_Id, tweet_Id, page_type })
+            this.addDomView({ tweet_Id, post_Id, element: ToolBox, parentElement: dom })
+        }
+    }
     replaceDOMRedPacket({ dom_card, tweet_Id, post_Id, time, short_url, page_type = '' }) {
         if (!dom_card || !dom_card.parentElement) {
             return
@@ -408,7 +460,7 @@ class ParseCard {
 
             if (post_Id.indexOf('luckdraw/') >= 0) {
                 post_Id = post_Id.replace('luckdraw/', '');
-                dom.appendChild(this.createIframe({ post_Id, tweet_author, page_type:'抽奖' }, true))
+                dom.appendChild(this.createIframe({ post_Id, tweet_author, page_type: '抽奖' }, true))
             } else {
                 dom.appendChild(this.createIframe({ post_Id, tweet_author }, true))
             }

+ 26 - 0
src/logic/content/ToolBox.js

@@ -0,0 +1,26 @@
+import ToolBoxFull from '@/view/content/tool-box/full.vue'
+import { createApp } from 'vue'
+
+
+export class ToolBox {
+    constructor() {
+
+    }
+    // 加载组件
+    initFull() {
+        const div = document.createElement('div')
+        div.id = 'denet-tool-box-fixed'
+        div.style.cssText = `
+            width: 505px;
+            height: 544px;
+            position: fixed;
+            right: 10px;
+            top: 10px;
+            display:none;`
+        document.body.appendChild(div)
+        createApp(ToolBoxFull).mount(`#${div.id}`)
+    }
+}
+
+
+export default new ToolBox()

+ 115 - 73
src/logic/content/twitter.js

@@ -5,9 +5,9 @@ import { reportSrcPublishEvent } from '@/http/publishApi'
 import Report from "@/log-center/log"
 import { fetchAddFinishEvent } from '@/logic/background/fetch/facebook';
 import { showNFTGroupIcon, hideNFTGroupList, checkUserJoinGroup, elemAddEventListener, addJoinedGroupList } from '@/logic/content/nft';
-import { getTwitterNftGroupInfo } from '@/http/nft'
 import { jumpTwitterDetailByAlert, showEditTweet } from '@/logic/content/help/twitter.js'
 import { clearPostContent, setGroupIconStatus } from '@/logic/content/nft.js'
+import ToolBox from '@/logic/content/ToolBox'
 import axios from 'axios';
 
 let dom = {};
@@ -73,14 +73,14 @@ function renderDom() {
 /**
  * 展示give弹窗
  */
-export function showGiveDialogHandler(userInfo) {
+export function showGiveDialogHandler(params) {
     let iframe = document.getElementById('iframe-content');
     if (iframe) {
-        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', userInfo }, '*');
+        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', data: params }, '*');
     } else {
         _addIframe();
         let iframe = document.getElementById('iframe-content');
-        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', userInfo }, '*');
+        iframe.contentWindow.postMessage({ actionType: 'CONTENT_SHOW_GIVE_DIALOG', data: params }, '*');
     }
 }
 
@@ -233,6 +233,10 @@ function _publishTweetEvent(params, cb) {
 }
 
 function onClosePublishDialogHandle(dom, params) {
+    // 如果是 Tool box
+    if (params.postType == 3) {
+        return;
+    }
     dom.querySelector('div[role="group"]').addEventListener('click', function () {
         setTimeout(() => {
             let parent = document.querySelector('div[data-testid="confirmationSheetDialog"]');
@@ -273,9 +277,10 @@ function checkIsShowReSend(dom, params) {
  * @param isClick
  * @private
  */
-function _addDeNetEditBtn(parent, dom, isClick = false) {
+function _addDeNetEditBtn(params = {}) {
     setTimeout(() => {
-        if (parent && parent.parentNode) {
+        let toolElem = document.querySelector('div[data-testid="toolBar"]');
+        if (toolElem) {
             Report.reportLog({
                 pageSource: Report.pageSource.mainPage,
                 businessType: Report.businessType.buttonView,
@@ -283,12 +288,17 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
             });
             let innerDeIcon = document.getElementById('de-btn1');
             if (!innerDeIcon) {
-                parent.parentNode.insertBefore(dom, parent.nextElementSibling);
+                toolElem.firstChild.appendChild(createTweetToolbarDenet())
+            }
+
+            let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+            if (!innerToolBoxIcon) {
+                toolElem.firstChild.appendChild(createTweetToolbarToolBox())
             }
         } else {
             setTimeout(() => {
-                parent = _getScheduleDom(isClick);
-                if (parent && parent.parentNode) {
+                let toolElem = document.querySelector('div[data-testid="toolBar"]');
+                if (toolElem) {
                     Report.reportLog({
                         pageSource: Report.pageSource.mainPage,
                         businessType: Report.businessType.buttonView,
@@ -296,7 +306,12 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
                     });
                     let innerDeIcon = document.getElementById('de-btn1');
                     if (!innerDeIcon) {
-                        parent.parentNode.insertBefore(dom, parent.nextElementSibling);
+                        toolElem.firstChild.appendChild(createTweetToolbarDenet())
+                    }
+
+                    let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+                    if (!innerToolBoxIcon) {
+                        toolElem.firstChild.appendChild(createTweetToolbarToolBox())
                     }
                 }
             }, 1000)
@@ -304,16 +319,6 @@ function _addDeNetEditBtn(parent, dom, isClick = false) {
     })
 }
 
-/**
- * 在dialog插入deNet按钮
- * @private
- */
-// function _addDeNetBtnToDialog() {
-//     setTimeout(() => {
-//         let dialogScheduleBtn = _getScheduleDom(true);
-//         _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn2);
-//     }, 800)
-// }
 
 /**
  * 获取左侧twitter按钮
@@ -335,9 +340,9 @@ function _addDeNetBtn() {
         let navWidth = document.querySelector('nav[role="navigation"]').offsetWidth;
         addSliderNavDeBtn(navWidth < 245);
         let innerDeIcon = document.getElementById('de-btn1');
-        if (!innerDeIcon) {
-            let dialogScheduleBtn = _getScheduleDom(false);
-            dom && dom.deBtn1 && _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn1);
+        let innerToolBoxIcon = document.getElementById('de-tool-box-btn-01');
+        if (!innerDeIcon || !innerToolBoxIcon) {
+            _addDeNetEditBtn();
         }
     }, 800)
 }
@@ -430,7 +435,7 @@ export function hideNoticeBindTweet() {
  * 点击deNet按钮处理
  * @private
  */
-function _deNetBtnClick() {
+function _deNetBtnClick(params = {}) {
     getUserInfo((res) => {
         if (res) {
             if (window.location.pathname != '/home') {
@@ -439,7 +444,7 @@ function _deNetBtnClick() {
                 }
                 dom.homeBtn.click();
             }
-            showGiveDialogHandler(res);
+            showGiveDialogHandler(params);
         } else {
             let loadIcon = document.getElementById('de-btn-loading');
             if (loadIcon) {
@@ -526,17 +531,13 @@ function _createBtnDom() {
     deBtn.style.cssText = 'width:90%;height: 52px;text-align:center;line-height:52px;margin-bottom: 4px;margin-top: 4px;background: linear-gradient(274.8deg, #FF9900 -3.69%, #BD00FF 69.71%, #00F0FF 122.65%);color:#fff;font-size:17px;font-weight:700;border-radius:100px;cursor: pointer;display: flex;align-items: center;justify-content: center;';
 
     // 编辑框内按钮
-    const deBtn1 = document.createElement('img');
     let src = require("@/assets/img/icon-gift-pack.png");
     const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
-    deBtn1.id = 'de-btn1';
-    deBtn1.style.cssText = smallDeBtnStyle;
-    deBtn1.src = src
 
     const deBtn2 = document.createElement('div');
     deBtn2.id = 'de-btn2';
     deBtn2.style.cssText = smallDeBtnStyle;
-    deBtn2.src = "@/assets/img/icon-gift-pack.png"
+    deBtn2.src = src
 
     // 小屏按钮
     const deBtn3 = document.createElement('img');
@@ -545,29 +546,14 @@ function _createBtnDom() {
     deBtn3.style.cssText = 'width:52px;height: 52px;margin-top:20px;cursor: pointer;';
 
     deBtn.addEventListener('click', () => {
-        // chrome.runtime.sendMessage({
-        //     actionType: 'CONTENT_SET_BADGE',
-        //     data: {
-        //         text: '2'
-        //     }
-        // }, res => {
-        //     console.log(res);
-        // })
-
         Report.reportLog({
             pageSource: Report.pageSource.mainPage,
             businessType: Report.businessType.buttonClick,
             objectType: Report.objectType.buttonMain
         });
-        _deNetBtnClick();
-    })
-    deBtn1.addEventListener('click', () => {
-        Report.reportLog({
-            pageSource: Report.pageSource.mainPage,
-            businessType: Report.businessType.buttonClick,
-            objectType: Report.objectType.buttonSecond
+        _deNetBtnClick({
+            type: 'SHOW_SELECT'
         });
-        _deNetBtnClick();
     })
     deBtn2.addEventListener('click', () => {
         _deNetBtnClick();
@@ -578,15 +564,54 @@ function _createBtnDom() {
             businessType: Report.businessType.buttonClick,
             objectType: Report.objectType.buttonMain
         });
-        _deNetBtnClick();
+        _deNetBtnClick({
+            type: 'SHOW_SELECT'
+        });
     })
     dom.deBtn = deBtn;
-    dom.deBtn1 = deBtn1;
     dom.deBtn2 = deBtn2;
     dom.deBtn3 = deBtn3;
     dom.loadingImg = loadingImg;
 }
 
+function createTweetToolbarDenet() {
+    let src = require("@/assets/img/icon-gift-pack.png");
+    const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
+
+    const deBtn1 = document.createElement('img');
+    deBtn1.id = 'de-btn1';
+    deBtn1.style.cssText = smallDeBtnStyle;
+    deBtn1.src = src;
+
+    deBtn1.addEventListener('click', () => {
+        Report.reportLog({
+            pageSource: Report.pageSource.mainPage,
+            businessType: Report.businessType.buttonClick,
+            objectType: Report.objectType.buttonSecond
+        });
+        _deNetBtnClick();
+    })
+
+    return deBtn1;
+}
+
+function createTweetToolbarToolBox() {
+    let src = require("@/assets/img/icon-tool-box-01.png");
+    const smallDeBtnStyle = 'width:20px;height: 20px;cursor: pointer;padding: 0px 8px';
+
+    const deToolBoxBtn = document.createElement('img');
+    deToolBoxBtn.id = 'de-tool-box-btn-01';
+    deToolBoxBtn.style.cssText = smallDeBtnStyle;
+    deToolBoxBtn.src = src;
+
+    deToolBoxBtn.addEventListener('click', () => {
+        _deNetBtnClick({
+            type: 'TOOL_BOX'
+        })
+    })
+
+    return deToolBoxBtn;
+}
 async function addSliderNavDeBtn(isSmall = false) {
     try {
         if (!isSmall) {
@@ -625,7 +650,7 @@ async function addSliderNavDeBtn(isSmall = false) {
 
 function onWindowResize() {
     window.onresize = throttle(function () {
-        setTabGroupIframeStyle();
+        setTabGroupIframeStyle({}, true);
         try {
             if (tweetPublishStore.showPublishDialog) {
                 let dialog = document.querySelector('div[role="dialog"]');
@@ -665,8 +690,7 @@ function checkHasDeBtn() {
         let toolBar = document.querySelector('div[data-testid="toolBar"]');
         let innerDeIcon = document.getElementById('de-btn1');
         if (toolBar && !innerDeIcon) {
-            let dialogScheduleBtn = _getScheduleDom(false);
-            dom && dom.deBtn1 && _addDeNetEditBtn(dialogScheduleBtn, dom.deBtn1);
+            _addDeNetEditBtn();
         }
     } catch (e) {
         console.log(e)
@@ -724,6 +748,10 @@ export const changeQueueNum = (num = 0) => {
 
 let main_observer = null
 function onChangePageMain(targetNode) {
+    if (main_observer) {
+        return
+    }
+    changeQueueNum(1)
     try {
         const config = { attributes: false, childList: true, subtree: true };
         const callback = (mutationsList, observer) => {
@@ -764,7 +792,11 @@ function setIframeRedPacket(type = 'twitter') {
                         item.post_Id = item.post_Id.split('luckdraw/')[1] || ''
                         item.page_type = '抽奖'
                         parseCard.replaceDOMRedPacket(item)
-                    } else {
+                    } else if (item && item.post_Id && item.post_Id.indexOf('toolbox/') >= 0) {
+                        item.page_type = 'toolbox'
+                        item.post_Id = item.post_Id.split('toolbox/')[1] || ''
+                        parseCard.replaceDomView(item)
+                    } else if (item && item.post_Id && !item.post_Id.includes('/')) {
                         item.page_type = '红包'
                         parseCard.replaceDOMRedPacket(item)
                     }
@@ -838,10 +870,7 @@ function initParseCard() {
         if (inTwitter && inTwitterNode) {
             clearInterval(timer)
             setInterval(() => {
-                if (!main_observer) {
-                    onChangePageMain(inTwitterNode)
-                    changeQueueNum(1)
-                }
+                onChangePageMain(inTwitterNode)
                 twitterPinLogin()
                 showNFTGroupIcon()
                 if (queue_num <= 0) {
@@ -859,10 +888,7 @@ function initParseCard() {
         } else if (inFacebook && inFacebookNode) {
             clearInterval(timer)
             setInterval(() => {
-                if (!main_observer) {
-                    onChangePageMain(inFacebookNode)
-                    changeQueueNum(1)
-                }
+                onChangePageMain(inTwitterNode)
                 if (queue_num <= 0) {
                     return
                 }
@@ -872,6 +898,7 @@ function initParseCard() {
         }
     }, 1000);
 }
+
 let inited = false
 // 初始化
 export function init() {
@@ -897,16 +924,18 @@ export function init() {
     }
 
     if (window.location.host.includes('twitter.com')) {
+        renderDom();
         showNFTCard()
         showNFTGroupIcon()
         addEventAction();
         checkUserJoinGroup();
-        renderDom();
         checkTwitterTaskState();
         initBuyNFT();
         addJoinedGroupList();
         getSysTheme();
         addGroupTab();
+        // 预加载全屏 toobbox
+        ToolBox.initFull()
     }
     // 渲染dom
     initParseCard()
@@ -1762,15 +1791,6 @@ export const tiggerInjectPopupPage = () => {
     }
 }
 
-const onBodyClick = () => {
-    if (window.location.href.indexOf('api.twitter.com') < 0) {
-        document.querySelector('body').addEventListener('click', function () {
-            console.log('click')
-            // hidePopupPage();
-        })
-    }
-}
-
 export const setPopupConfByPopupPage = () => {
     let iframe = document.getElementById('de-popup-page');
     if (iframe) {
@@ -1980,7 +2000,12 @@ export const groupTipsSelectGroupTab = (params = {}) => {
 const addGroupTabEventListener = () => {
     let groupTab = getGroupTabNode();
     groupTab.addEventListener('click', function () {
-        let groupColor = systemInfo.theme == 'light' ? 'rgb(15, 20, 25)' : '#fff';
+        let bgColor = document.querySelector('body').style.backgroundColor;
+
+        let groupColor = systemInfo.theme == 'dark' && bgColor == 'rgb(0, 0, 0)' ? '#fff' : 'rgb(15, 20, 25)';
+
+        // let groupColor = systemInfo.theme == 'light' ? 'rgb(15, 20, 25)' : '#fff';
+
         setGroupTabSelfStyle({
             groupColor: groupColor,
             groupFontWeight: '700',
@@ -2265,7 +2290,10 @@ const fixProfileTabAutoSwitch = () => {
  * 
  * 设置Tab Group Iframe 样式
  */
-export const setTabGroupIframeStyle = (params) => {
+export const setTabGroupIframeStyle = (params, isReSize = false) => {
+    if (!isReSize) {
+        getSysTheme();
+    }
     let iframeContent = getGroupTabContentNode();
     if (iframeContent) {
         let htmlHeight = document.querySelector('html').offsetHeight;
@@ -2339,6 +2367,7 @@ const getSysTheme = () => {
     } else {
         systemInfo.theme = 'dark'
     }
+    sysThemeChange();
     themeMedia.addListener(e => {
         addGroupTab()
         if (e.matches) {
@@ -2346,9 +2375,22 @@ const getSysTheme = () => {
         } else {
             systemInfo.theme = 'dark'
         }
+        sysThemeChange();
     });
 }
 
+const sysThemeChange = () => {
+    setTimeout(() => {
+        let bgColor = document.querySelector('body').style.backgroundColor;
+
+        chrome.runtime.sendMessage({
+            actionType: "CONTENT_SYS_THEME_CHANGE", data: {
+                theme: systemInfo.theme,
+                twitterTheme: bgColor == 'rgb(0, 0, 0)' ? 'dark' : 'light'
+            }
+        }, () => { })
+    }, 800)
+}
 
 /** 
  * 

+ 18 - 2
src/manifest.json

@@ -2,7 +2,7 @@
     "manifest_version": 3,
     "name": "DeNet",
     "description": "Growing more twitter followers with Denet",
-    "version": "1.1.3",
+    "version": "1.1.4",
     "background": {
         "service_worker": "/js/background.js"
     },
@@ -44,7 +44,19 @@
             ]
         }
     ],
+    "optional_permissions":[
+        "declarativeNetRequest"
+    ],
+    "declarative_net_request" : {
+        "rule_resources" : [{
+          "id": "ruleset_1",
+          "enabled": true,
+          
+          "path": "/rules/rules_1.json"
+        }]
+      },
     "host_permissions": [
+        "*://*/*",
         "<all_urls>",
         "*://*.twitter.com/*",
         "*://twitter.com/*",
@@ -58,6 +70,8 @@
         "tabs",
         "action",
         "cookies",
+        "webNavigation",
+        "declarativeNetRequest",
         "activeTab",
         "scripting",
         "storage",
@@ -80,7 +94,9 @@
                 "/iframe/popup-page.html",
                 "/iframe/popup-page.html",
                 "/iframe/tab-group.html",
-                "/iframe/joined-group-list.html"
+                "/iframe/joined-group-list.html",
+                "/iframe/tool-box-guide.html",
+                "/iframe/test.html"
             ],
             "matches": [
                 "<all_urls>"

+ 9 - 4
src/rules/rules_1.json

@@ -1,17 +1,22 @@
 [
     {
         "id": 1,
-        "priority":1,
         "condition": {
-            "urlFilter": "twitter.com"
         },
         "action": {
             "type": "modifyHeaders",
             "responseHeaders": [
                 {
                     "header": "X-Frame-Options",
-                    "operation": "set",
-                    "value":"ALLOW-FROM  https://twitter.com"
+                    "operation": "remove"
+                },        
+                {
+                    "header": "Frame-Options",
+                    "operation": "remove"
+                },
+                {
+                    "header": "content-security-policy",
+                    "operation": "remove"
                 }
             ]
         }

+ 36 - 5
src/uilts/chromeExtension.js

@@ -1,5 +1,6 @@
 import { pageUrl } from "@/http/configAPI.js"
-
+import { exp } from "mathjs"
+import { guid } from "@/uilts/help";
 export const LANDING_PAGE = {
     name: 'received_log',
     url: pageUrl
@@ -10,8 +11,12 @@ export const LANDING_PAGE_MID = {
     url: pageUrl
 }
 
-export function setChromeStorage(params) {
-    chrome.storage.local.set(params)
+export function setChromeStorage(params, callback) {
+    if (callback) {
+        chrome.storage.local.set(params, callback)
+    } else {
+        chrome.storage.local.set(params)
+    }
 }
 
 export function getChromeStorage(key = '', callback) {
@@ -107,7 +112,7 @@ export function removeChromeCookie(params, cb) {
     })
 }
 
-export function sendChromeTabMessage(params,callback) {
+export function sendChromeTabMessage(params, callback) {
     chrome.tabs.getCurrent((tab) => {
         chrome.tabs.sendMessage(tab.id, params, callback);
     })
@@ -130,4 +135,30 @@ export function checkIsLogin(callback) {
             }
         })
     })
-}
+}
+
+let callback_arr = []
+export function httpContentToBack(data, callback) {
+    let callback_id = guid()
+    chrome.runtime.sendMessage({ actionType: "HTTP_CONTENT_TO_BACK", data, callback_id }, (res) => {
+        if (res && callback) {
+            callback_arr.push({
+                callback_id,
+                callback,
+            })
+        }
+    })
+}
+
+// 再找到它执行
+export function httpBackToContentCallBack(req) {
+    for (let i in callback_arr) {
+        if (callback_arr[i].callback_id == req.callback_id) {
+            // 执行
+            callback_arr[i].callback(req.data)
+            // 删除
+            callback_arr.splice(i, 1)
+            break
+        }
+    }
+}

+ 12 - 0
src/uilts/event.js

@@ -0,0 +1,12 @@
+export const sendEventInfo = (data) => {
+    // 创建事件
+    let onEvent = new CustomEvent("onEvent", {
+        detail: data
+    });
+    window.dispatchEvent(onEvent);
+}
+
+// window.addEventListener("onEvent", e => {
+//     alert(`pingan事件触发,是 ${e.info.name} 触发。`);
+//     // dom之间互相传至
+// })

+ 13 - 0
src/uilts/help.js

@@ -216,3 +216,16 @@ export function formatSecondsAsDayHour(secs) {
   }
   return text;
 }
+
+export function checkURL(URL) {
+  var str = URL;
+  //判断URL地址的正则表达式为:http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
+  // /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/
+  var Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
+  var objExp = new RegExp(Expression);
+  if (objExp.test(str) == true) {
+      return true;
+  } else {
+      return false;
+  }
+}

+ 1 - 1
src/view/components/global-tip.vue

@@ -33,7 +33,7 @@ onMounted(() => {
 <style lang="scss" scoped>
 .global-tip {
     position: absolute;
-    z-index: 999;
+    z-index: 3000;
     display: flex;
     height: 40px;
     background: #000000;

+ 150 - 0
src/view/content/tool-box/full.vue

@@ -0,0 +1,150 @@
+<template>
+    <div class="denet-toolbox">
+        <div class="head">
+            <span></span>
+            <div>
+                <!-- 缩放 -->
+                <img :src="require('@/assets/img/icon-full.png')" alt class="fixed" @click="clickFull" />
+                <!-- 关闭 -->
+                <img :src="require('@/assets/img/icon-close.png')" alt class="full" @click="clickClose" />
+            </div>
+        </div>
+        <div class="content">
+            <iframe :src="state.iframe_url" frameborder="0"></iframe>
+        </div>
+    </div>
+</template>
+<script setup>
+import { onMounted, reactive, ref } from "vue";
+import { sendEventInfo } from "@/uilts/event";
+let state = reactive({
+    status: '固定', // 全屏
+    iframe_url: '',
+    tweetId: ''
+})
+let dom_fixed = null
+
+window.addEventListener("onEvent", e => {
+    let info = e.detail
+    switch (info.event_type) {
+        // 固定
+        case 'ToolBox_To_Fixed':
+            // 替换
+            if (state.iframe_url || state.tweetId) {
+                sendClose()
+            }
+            state.iframe_url = info.data.iframe_url
+            state.tweetId = info.data.tweetId
+            break
+    }
+});
+
+
+const clickFull = () => {
+    if (state.status == '固定') {
+        state.status = '全屏'
+        changeFull()
+    } else {
+        state.status = '固定'
+        changeFixed()
+    }
+}
+onMounted(() => {
+    dom_fixed = document.querySelector('#denet-tool-box-fixed')
+})
+
+const changeFull = () => {
+    dom_fixed.style.cssText = `
+    width:100%;
+    height: 100%;
+    position: fixed;
+    right: 0px;
+    top: 0px;`
+}
+
+const changeFixed = () => {
+    dom_fixed.style.cssText = `
+    width: 505px;
+    height: 544px;
+    position: fixed;
+    right: 10px;
+    top: 10px;`
+}
+
+const clickClose = () => {
+    dom_fixed.style.display = 'none'
+    sendClose()
+
+}
+const sendClose = () => {
+    let url = state.iframe_url
+    let tweetId = state.tweetId
+    sendEventInfo({
+        event_type: 'ToolBox_Fixed_Close',
+        data: {
+            url,
+            tweetId,
+        }
+    })
+    state.iframe_url = ''
+    state.tweetId = ''
+}
+
+
+</script>
+
+<style lang="scss">
+.denet-toolbox {
+    width: 100%;
+    height: 100%;
+    filter: drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.2));
+    border-radius: 12px;
+    overflow: hidden;
+
+    .head {
+        width: 100%;
+        height: 40px;
+        background: #373737;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        img {
+            width: 20px;
+            height: 20px;
+            cursor: pointer;
+
+        }
+
+        .full {
+            margin-right: 16px;
+        }
+
+        .fixed {
+            margin-right: 20px;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 40px);
+        background: #686868;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        iframe {
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+</style>

+ 436 - 0
src/view/content/tool-box/index.vue

@@ -0,0 +1,436 @@
+<template>
+    <div class="denet-toolbox" @click.stop="clickHead" ref="dom_toolbox" :class="{ 'pre-view': pre_view }">
+        <div class="head" @click.stop="clickHead">
+            <span></span>
+            <div v-show="state.show_btn && state.status == ''">
+                <img :src="require('@/assets/img/icon-fixed.png')" alt class="fixed" @click.stop="clickFixed" />
+                <img :src="require('@/assets/img/icon-full.png')" alt class="full" @click.stop="clickFull" />
+            </div>
+        </div>
+        <div class="content" v-if="pre_view">
+            <iframe :src="iframe_url" frameborder="0"></iframe>
+        </div>
+        <div class="content" v-else>
+            <iframe :src="state.iframe_url" v-show="state.status == ''" ref="dom_iframe" frameborder="0"></iframe>
+            <!-- 网页错误 -->
+            <div class="state" v-show="state.status == '网页错误'">
+                <img :src="require('@/assets/img/icon-page-fail.png')" alt />
+                <div>Oops, this link is invalid</div>
+            </div>
+
+            <!-- 加载 -->
+            <div class="state" v-show="state.status == '加载'">
+                <img :src="require('@/assets/img/icon-loading-gray.png')" alt class="icon-loading" />
+            </div>
+
+            <!-- 关闭 -->
+            <div class="state" v-show="state.status == '关闭'">
+
+            </div>
+            <!-- 固定右上角 -->
+            <div class="state" v-show="state.status == '固定右上角'">
+                <img :src="require('@/assets/img/icon-fixed-gray.png')" alt />
+                <div>Pinned to the top right</div>
+            </div>
+        </div>
+        <!-- alert -->
+        <div class="alert" v-show="state.show_alert">
+            <div class="back" @click.stop="clickCancel"></div>
+            <div class="confirm">
+                <div class="check">
+                    <input :id="state.checkbox_id" type='checkbox' v-model="state.checkbox" />
+                    <label :for="state.checkbox_id">Don't remind</label>
+                </div>
+
+                <div class="title">Web Page Progress May Reset</div>
+                <div class="handle">
+                    <div class="cancel" @click.stop="clickCancel">Cancel</div>
+                    <div class="continue" @click.stop="clickContinue">Continue</div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script setup>
+import { getChromeStorage, setChromeStorage, httpContentToBack, defineProps } from "@/uilts/chromeExtension";
+import { guid } from "@/uilts/help";
+import { sendEventInfo } from "@/uilts/event";
+import { onMounted, reactive, ref } from "vue";
+let dom_toolbox = ref(null)
+let dom_iframe = ref(null)
+let state = reactive({
+    status: '', //
+    show_alert: false,
+    show_btn: true,
+    list: [],
+    checkbox: false,
+    checkbox_id: `denet-${guid()}`,
+    postId: '',
+    tweetId: '',
+    detail: {},
+    handle_type: ''
+})
+
+let dom = {}
+
+let props = defineProps({
+    pre_view: {
+        type: Boolean,
+        default: false,
+    },
+    iframe_url: {
+        type: String,
+        default: ''
+    }
+})
+
+window.addEventListener("onEvent", e => {
+    let info = e.detail
+    switch (info.event_type) {
+        // 事件传输
+        case 'ToolBox_Fixed_Close':
+            if (info.data.tweetId == state.tweetId) {
+                state.show_btn = true
+                state.status = ''
+                state.iframe_url = info.data.url
+            }
+            break
+    }
+});
+const clickHead = () => {
+    // 
+}
+
+const clickContinue = () => {
+    if (state.checkbox) {
+        setChromeStorage({ fullCheck: JSON.stringify({ fullCheck: 1 }) })
+    }
+    if (state.handle_type == '全屏') {
+        handleFull()
+    } else {
+        handleFixed()
+    }
+    state.show_alert = false
+}
+onMounted(() => {
+    if (props.pre_view) {
+        return
+    }
+    if (dom_toolbox.value) {
+        state.dom_root = dom_toolbox.value.closest('div[data-tweet-id]')
+        if (state.dom_root) {
+            state.postId = state.dom_root.dataset.postId || ''
+            state.tweetId = state.dom_root.dataset.tweetId || ''
+        }
+    }
+    try {
+        dom.fixed = document.querySelector('#denet-tool-box-fixed')
+        if (dom.fixed && dom.fixed.style.display == 'block') {
+            if (dom.fixed.dataset.tweetId == state.tweetId) {
+                state.status = '固定右上角'
+                return
+            }
+        }
+    }catch(error){
+        console.log(error)
+    }
+    getDetail()
+})
+
+// detail函数
+const getDetail = () => {
+    state.status = '加载'
+    httpContentToBack({
+        url: `/post/getDetail`,
+        params: {
+            postId: state.postId
+        }
+    }, (res) => {
+        if (res && res.code == 0) {
+            state.detail = JSON.parse(res.data.postBizData)
+            console.log('postBizData', state.detail)
+            // 加载iframe
+            let iframe = dom_iframe.value
+            // state.detail.convertUrl = 'https://www.bilibili.com'
+            // iframe.onerror = () => {
+            //     state.status = '网页错误'
+            // }
+            // iframe.onload = function () {
+            //     if (state.status != '固定右上角') {
+            //         state.status = ''
+            //     }
+            // }
+            setTimeout(() => {
+                state.iframe_url = state.detail.convertUrl
+                state.status = ''
+            }, 1000)
+        } else {
+            state.status = '网页错误'
+        }
+    })
+}
+
+const clickCancel = () => {
+    state.show_alert = false
+}
+
+const clickFixed = () => {
+    state.handle_type = '固定'
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 固定
+            handleFixed()
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+// 固定
+const handleFull = () => {
+    if (state.status || !state.iframe_url) {
+        return
+    }
+    // 切换状态
+    state.status = '关闭'
+    // 操作全屏dom
+    dom.fixed.style.cssText = `
+        width:100%;
+        height: 100%;
+        position: fixed;
+        right: 0px;
+        top: 0px;
+        display:block;`
+    dom.fixed.dataset.tweetId = state.tweetId
+    state.show_btn = false
+    sendEventInfo({
+        event_type: 'ToolBox_To_Fixed',
+        data: {
+            iframe_url: state.iframe_url,
+            tweetId: state.tweetId
+        }
+    })
+    // 清除当前iframe src
+    state.iframe_url = ''
+}
+
+// 全屏
+const handleFixed = () => {
+    // 切换状态
+    state.show_btn = false
+    state.status = '固定右上角'
+    // 操作全屏dom
+    dom.fixed.style.cssText = `
+        display:block;
+        width: 505px;
+        height: 544px;
+        position: fixed;
+        right: 10px;
+        top: 10px;`
+    dom.fixed.dataset.tweetId = state.tweetId
+    sendEventInfo({
+        event_type: 'ToolBox_To_Fixed',
+        data: {
+            iframe_url: state.iframe_url,
+            tweetId: state.tweetId
+        }
+    })
+    // 清除当前iframe src
+    state.iframe_url = ''
+}
+
+const clickFull = () => {
+    state.handle_type = '全屏'
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 全屏
+            handleFull()
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+</script>
+
+<style lang="scss">
+.pre-view {
+    pointer-events: none;
+    cursor: default;
+}
+
+.denet-toolbox {
+    width: 100%;
+    height: 100%;
+    filter: drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.2));
+    border-radius: 12px;
+    overflow: hidden;
+    position: relative;
+
+    .alert {
+        text-align: center;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+
+        .back {
+            background: #000000;
+            opacity: 0.8;
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            cursor: auto;
+        }
+
+        .confirm {
+            position: absolute;
+            width: 355px;
+            height: 180px;
+            background: #FFFFFF;
+            border-radius: 20px;
+            top: 173px;
+            left: 50%;
+            margin-left: -180px;
+
+            .title {
+                font-weight: 600;
+                font-size: 18px;
+                color: #000000;
+                margin-bottom: 34px;
+            }
+
+            .check {
+                color: #899099;
+                font-weight: 400;
+                font-size: 14px;
+                margin: 12px 15px 32px 0;
+                text-align: right;
+                align-content: center;
+                justify-content: flex-end;
+                display: flex;
+                line-height: 17px;
+
+                input {
+                    margin-right: 8px;
+                }
+
+                label {
+                    line-height: 19px;
+                }
+
+            }
+
+            .handle {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+
+                div {
+                    font-weight: 600;
+                    font-size: 16px;
+                    width: 156px;
+                    height: 47px;
+                    line-height: 47px;
+                    cursor: pointer;
+                    border-radius: 1000px;
+                    user-select: none;
+                }
+
+                .cancel {
+                    color: #000000;
+                    background: rgba(56, 154, 255, 0.01);
+                    border: 1px solid #E6E6E6;
+                }
+
+                .continue {
+                    background: #1D9BF0;
+                    font-weight: 600;
+                    margin-left: 11px;
+                    color: #FFFFFF;
+                }
+            }
+        }
+    }
+
+    .head {
+        width: 100%;
+        height: 40px;
+        background: #373737;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        img {
+            width: 20px;
+            height: 20px;
+            cursor: pointer;
+
+        }
+
+        .full {
+            margin-right: 16px;
+        }
+
+        .fixed {
+            margin-right: 20px;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 40px);
+        background: #686868;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        iframe {
+            width: 100%;
+            height: 100%;
+        }
+
+        .state {
+            text-align: center;
+
+            img {
+                margin-bottom: 14px;
+            }
+
+            .icon-loading {
+                animation: loading 1s infinite linear;
+            }
+
+            div {
+                margin-bottom: 40px;
+                color: #8E8E8E;
+                text-align: center;
+                font-weight: 500;
+                font-size: 22px;
+            }
+        }
+
+    }
+}
+
+@keyframes loading {
+    from {
+        transform: rotate(0deg);
+    }
+
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

+ 141 - 0
src/view/iframe/publish/components/select-publish-content.vue

@@ -0,0 +1,141 @@
+<template>
+    <div class="overlay" v-if="visible">
+        <div class="content">
+            <!-- 头部 -->
+            <div class="head">
+                <div class="left">
+                    <!-- 关闭按钮 -->
+                    <div class="close-btn" @click="close">
+                        <img  class="icon-close"
+                            :src="require('@/assets/svg/icon-close.svg')"/>
+                    </div>
+                    <!-- 标题 -->
+                    <div class="title">
+                        DeNet
+                    </div>
+                </div>
+            </div>
+            <div class="body">
+                <div v-for="(item, index) in list"
+                    :key="index"
+                    class="item"
+                    @click="clickItem(item, index)">
+                    <img :src="item.icon" alt="">
+                    {{item.txt}}
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, watch, reactive, defineProps, onMounted } from "vue";
+let list = reactive([
+    {
+        icon: require('@/assets/svg/img-select-giveaway.svg'),
+        type: 'REDPACKET',
+        txt: 'Giveaway'
+    },
+    {
+        icon: require('@/assets/svg/img-select-tool-box.svg'),
+        type: 'TOOL_BOX',
+        txt: 'Tool Box'
+    }
+])
+
+const props = defineProps({
+    visible: {
+        type: Boolean,
+        default: false
+    },
+});
+
+
+const emits = defineEmits(["close", "select"]);
+
+const close = () => {
+    emits('close', {});
+}
+
+const clickItem = (params, index) => {
+    emits('select', params);
+}
+
+</script>
+
+<style lang="scss" scoped>
+.overlay {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1000;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    overflow: auto;
+
+    .content {
+        width: 580px;
+        height: 400px;
+        background: #ffffff;
+        border-radius: 20px;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        box-sizing: border-box;
+        z-index: 2000;
+
+        .head {
+            border-bottom: 1px solid #ececec;
+            height: 48px;
+            box-sizing: border-box;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 14px;
+
+            .left {
+                display: flex;
+                align-items: center;
+                .title {
+                    font-size: 16px;
+                    font-weight: 500;
+                }
+
+                .close-btn {
+                    display: flex;
+                    align-items: center;
+                    width: max-content;
+                    margin-right: 12px;
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .body {
+            display: flex;
+            justify-content: space-between;
+            height: calc(100% - 48px);
+            width: 350px;
+            margin: 0 auto;
+
+            .item {
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                font-weight: 500;
+                font-size: 18px;
+                margin-top: 70px;
+                cursor: pointer;
+
+                img {
+                    margin-bottom: 20px;
+                }
+            }
+        }
+    }
+}
+</style>

+ 113 - 27
src/view/iframe/publish/give-dialog.vue

@@ -15,16 +15,26 @@
                 <div class="left">
                     <!-- 关闭按钮 -->
                     <div class="close-btn" @click="close">
-                        <img  class="icon-close"
+                        <template v-if="publishType == 'TOOL_BOX'">
+                            <img  class="icon-close"
+                            :src="require('@/assets/svg/icon-close.svg')"
+                            v-if="toolBoxPageData.activePage == 'EDITOR'"/>
+                            <img class="icon-close"
+                                :src="require('@/assets/svg/icon-back.svg')"
+                                v-else/>
+                        </template>
+                        <template v-else>
+                            <img  class="icon-close"
                             :src="require('@/assets/svg/icon-close.svg')"
                             v-if="showComType == 'default'"/>
-                        <img class="icon-close"
-                            :src="require('@/assets/svg/icon-back.svg')"
-                            v-else/>
+                            <img class="icon-close"
+                                :src="require('@/assets/svg/icon-back.svg')"
+                                v-else/>
+                        </template>
                     </div>
                     <!-- 标题 -->
                     <div class="title">
-                        {{ currentComData[showComType]["title"] }}
+                        {{publishType.value == 'REDPACKET' ? currentComData[showComType]["title"] : 'Tool Box' }}
                     </div>
                 </div>
                 <div class="right">
@@ -94,17 +104,31 @@
                             @selectCurrency="selectCurrencyAfter"></currency-select>
                     </div>
 
-                    <div class="left" v-if="showComType != 'preview'">
-                        <div class="gift-pack-wrapper">
-                            <img class="icon" :src="require('@/assets/svg/icon-gift-pack.svg')"/>
-                        </div>
-                        <div class="bottom">
+                    <div class="left" v-if="showComType != 'preview' && toolBoxPageData.activePage != 'PREVIEW'">
+                        <div class="tab-item" 
+                            :class="{'active-tab': item.type == publishType}"
+                            v-for="(item, index) in leftTabList" 
+                            :key="index"
+                            @click="clickLeftTab(item, index)"
+                            >
+                            <img class="icon" :src="item.icon"/>
                         </div>
                     </div>
 
                     <div class="right"  
-                        :class="{'fill-right': showComType == 'preview'}">
+                        :class="{'fill-right': showComType == 'preview' || toolBoxPageData.activePage == 'PREVIEW'}">
                         <global-tip :type="'2'"></global-tip>
+
+                        
+                        <template v-if="publishType == 'TOOL_BOX'">
+                            <tool-box 
+                                :pageData="{
+                                    'linkInputDescImage': toolBoxPageData.postEditorLinkInputDescImage
+                                }"
+                                :activePage="toolBoxPageData.activePage" @onPageChange="onToolBoxPageChange"
+                                @toolBoxPublishFinish="toolBoxPublishFinish"></tool-box>
+                        </template>
+                        <template v-else>
                         <div class="form-wrapper"  v-if="showComType == 'default'">
                             <div class="form-cell-item base-form-wrapper">
                                 <div class="title">
@@ -410,6 +434,7 @@
                             </paypal-button>
                             <div v-else class="btn-wrap" @click="payStatusHandle(1)"><div class="custom-submit">Confirm</div></div>
                         </div>
+                        </template>
                     </div>
                 </div>
             </div>
@@ -476,6 +501,7 @@ import "element-plus/es/components/loading/style/css";
 import 'vue-cropper/dist/index.css'
 import { VueCropper }  from "vue-cropper";
 import {create, all} from "mathjs";
+
 import messageBox from "@/view/components/message-box.vue";
 import currencyList from "@/view/components/currency-list.vue";
 import currencySelect from "@/view/components/currency-select.vue";
@@ -484,6 +510,7 @@ import followInput from "@/view/iframe/publish/components/follow-input";
 import paypalButton from "@/view/iframe/publish/components/paypal-button";
 import topUp from "@/view/iframe/publish/components/top-up.vue";
 import topUp2 from "@/view/iframe/publish/components/top-up2.vue";
+import toolBox from '@/view/iframe/publish/tool-box/index.vue'
 import giveawayPoster from '@/view/iframe/publish/components/giveaway-poster.vue';
 import GlobalTip from '@/view/components/global-tip.vue'
 import customCardCover from '@/view/components/custom-card-cover.vue'
@@ -513,6 +540,8 @@ let publishRes = reactive({});
 
 //弹窗是否展示
 let visible = ref(false);
+let publishType = ref('REDPACKET');
+
 
 //弹窗高度
 let dialogStyle = reactive({
@@ -720,10 +749,32 @@ let showCustomizedRewardEditPopup = ref(false);
 
 let lotteryMaxHourDuration = 168;
 
+let leftTabList = reactive([
+    {
+        icon: require('@/assets/svg/icon-gift-pack.svg'),
+        type: 'REDPACKET'
+    },
+    {
+        icon: require('@/assets/svg/icon-tool-box-02.svg'),
+        type: 'TOOL_BOX'
+    }
+])
+
+let toolBoxPageData = reactive({
+    activePage: 'EDITOR', //EDITOR PREVIEW
+    postEditorLinkInputDescImage: ''
+})
+
+
 const props = defineProps({
-    dialogVisible: {
-        type: Boolean,
-        default: false,
+    dialogData: {
+        type: Object,
+        default: () => {
+            return {
+                visible: false,
+                type: 'REDPACKET'
+            }
+        },
     },
 });
 
@@ -763,11 +814,12 @@ let currentArrowCpd = computed(() => {
 let isMoneyRewardCpd =computed(() => baseFormData.rewardType === RewardType.money);
 
 watch(
-    () => props.dialogVisible,
+    () => props.dialogData,
     (newVal) => {
-        console.log("watch", newVal);
-        visible.value = newVal;
-        if (newVal) {
+        visible.value = newVal.visible;
+
+        if (newVal.visible) {
+            publishType.value = newVal.type;
             Report.reportLog({
                 pageSource: Report.pageSource.publisherDialog,
                 businessType: Report.businessType.pageView,
@@ -785,18 +837,33 @@ watch(
         } else {
             clearInterval(timer.value);
         }
+    },
+    {
+        deep: true
     }
 );
 
-const emits = defineEmits(["close", "confirm", "payPalFinsh"]);
+const emits = defineEmits(["close", "confirm", "postPublishFinish"]);
 
 const close = () => {
-    if (showComType.value != "default") {
-        showComType.value = "default";
-        isBack.value = true;
+    if(publishType.value == 'TOOL_BOX') {
+        closeToolBoxPage()
     } else {
-        initParams();
+        if (showComType.value != "default") {
+            showComType.value = "default";
+            isBack.value = true;
+        } else {
+            initParams();
+            emits("close", false);
+        }
+    }
+};
+
+const closeToolBoxPage = () => {
+    if(toolBoxPageData.activePage == "EDITOR") {
         emits("close", false);
+    } else if(toolBoxPageData.activePage == "PREVIEW") {
+        toolBoxPageData.activePage = "EDITOR";
     }
 };
 
@@ -1212,6 +1279,8 @@ const initParams = () => {
     tempCurrentCurrencyInfo.value = {};
     currentCurrencyInfo.value = {};
 
+    publishType.value = 'REDPACKET';
+
     // clear discord value
     setDiscordIptTxt({text: ''});
 
@@ -1263,7 +1332,7 @@ const payStatusHandle = (payStatus) => {
     //支付状态 0:未支付,1:支付成功,2:支付失败,3:已关闭,4:已退款
     switch (payStatus) {
         case 1:
-            emits("payPalFinsh", { publishRes });
+            emits("postPublishFinish", { publishRes });
             showComType.value = "default";
             initParams();
             break;
@@ -1749,6 +1818,7 @@ const setFrontConfig = () => {
         if (res.code == 0) {
             paypalHtml.value = res.data.paypalHtml;
             lotteryMaxHourDuration = res.data.lotteryMaxHourDuration;
+            toolBoxPageData.postEditorLinkInputDescImage = res.data.postEditorLinkInputDescImage;
         }
     });
 };
@@ -1805,6 +1875,19 @@ const selectPublishMode = (params, index) => {
     setInputErrorMsg();
 }
 
+const clickLeftTab = (params, index) => {
+    publishType.value = params.type;
+}
+
+const onToolBoxPageChange = (params) => {
+    toolBoxPageData.activePage = params.page;
+};
+
+const toolBoxPublishFinish = (params) => {
+    toolBoxPageData.activePage = 'EDITOR';
+    emits("postPublishFinish", { publishRes: params.publishRes });
+}
+
 // 截图相关
 const showDialog = () => {
     cropperDialog.value = true;
@@ -2111,16 +2194,19 @@ onMounted(() => {
                 width: 50px;
                 display: flex;
                 flex-direction: column;
-                justify-content: space-between;
                 align-items: center;
 
-                .gift-pack-wrapper {
+                .tab-item {
                     width: 100%;
                     height: 55px;
-                    background: #f5f5f5;
                     display: flex;
                     align-items: center;
                     justify-content: center;
+                    cursor: pointer;
+                }
+
+                .active-tab {
+                    background-color: #D2EAFC;
                 }
 
                 .bottom {

+ 37 - 10
src/view/iframe/publish/publish.vue

@@ -2,25 +2,34 @@
 <template>
     <div class="main_app">
         <give-dialog
-            :dialogVisible="dialogVisible"
+            :dialogData="dialogData"
             @close="close"
-            @payPalFinsh="payPalFinsh"
-        ></give-dialog>
+            @postPublishFinish="finish" ></give-dialog>
+        <select-publish-content 
+            :visible="selectVisible" 
+            @close="hideSelectDialog"
+            @select="selectPublishType"></select-publish-content>
     </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
+import { ref, reactive } from "vue";
 import giveDialog from "@/view/iframe/publish/give-dialog.vue";
+import selectPublishContent from "@/view/iframe/publish/components/select-publish-content.vue";
 
-let dialogVisible = ref(false);
+let dialogData = reactive({
+    visible: false,
+    type: 'REDPACKET'
+});
+
+let selectVisible = ref(false);
 
 const close = () => {
-    dialogVisible.value = false;
+    dialogData.visible = false;
     hideIframe();
 };
 
-const payPalFinsh = (params) => {
+const finish = (params) => {
     close();
     window.parent.postMessage({ actionType: "IFRAME_SHOW_TWITTER_PUBLISH_DIALOG", publishRes: params.publishRes }, "*");
 };
@@ -29,11 +38,29 @@ const hideIframe = () => {
     window.parent.postMessage({ actionType: "IFRAME_HIDE_IFREME" }, "*");
 };
 
+const hideSelectDialog = () => {
+    selectVisible.value = false;
+    close();
+}
+
+const selectPublishType = (params) => {
+    selectVisible.value = false;
+    dialogData.visible = true;
+    dialogData.type = params.type;
+}
+
 window.addEventListener("message", function (event) {
-    console.log("addEventListener", event);
-    if (event.data && event.data.actionType == "CONTENT_SHOW_GIVE_DIALOG") {
+    let eventData = event.data;
+    if (eventData && eventData.actionType == "CONTENT_SHOW_GIVE_DIALOG") {
         window.parent.postMessage({ actionType: "IFRAME_SHOW_IFREME" }, "*");
-        dialogVisible.value = true;
+
+        let {type = 'REDPACKET'} = eventData.data || {};
+        if(type != 'SHOW_SELECT') {
+            dialogData.visible = true;
+            dialogData.type = type;
+        } else {
+            selectVisible.value = true;
+        }
     }
 });
 </script>

+ 416 - 0
src/view/iframe/publish/tool-box/child/editor.vue

@@ -0,0 +1,416 @@
+<template>
+  <div class="editor-wrapper">
+    <div class="top">
+      <div class="title">
+        Enter Link to Embed in Tweet
+      </div>
+      <div class="search-wrapper">
+        <input class="input" type="text" v-model="siteUrl" placeholder="Enter link">
+        <div class="btn" @click="searchHandler">
+          <img :src="require('@/assets/svg/icon-tool-box-search-arrow.svg')" />
+        </div>
+      </div>
+      <div class="desc">
+        <img :src="linkInputDescImage" alt="">
+      </div>
+    </div>
+    <div class="bottom">
+      <div class="content">
+        <div class="cate-item history-wrapper" v-if="historyList.length">
+          <div class="cate">
+            <img :src="require('@/assets/svg/icon-tool-app-history.svg')" />
+          </div>
+          <div class="app-list">
+            <div class="app" v-for="(app, idx) in historyList" :key="idx"
+              @click="clickHistoryAppHandler(app)">
+              <div class="img-wrapper">
+                <img class="img" :class="{'small-img': !app.appId}" :src="app.iconPath" :onerror="imgOnError" />
+              </div>
+              <div class="name">
+                {{ app.name }}
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="cate-item" v-for="(item) in appList" :key="item.cateId">
+          <div class="cate">
+            <img :src="item.iconPath">
+          </div>
+          <div class="app-list">
+            <div class="app" v-for="(app, idx) in item.apps" :key="idx" @click="clickAppHandler(app)">
+              <img class="app-img" :src="app.iconPath" alt="">
+              <div class="name">
+                {{ app.name }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits, onMounted } from "vue";
+import axios from 'axios';
+import { message } from "ant-design-vue";
+import { convertUrl, getAllPostEditorAppData } from "@/http/toolBoxApi";
+import { getChromeStorage } from "@/uilts/chromeExtension"
+import { checkURL } from "@/uilts/help"
+
+const props = defineProps({
+    linkInputDescImage: {
+        type: String,
+        default: '',
+    },
+});
+
+
+let siteUrl = ref('');
+
+let selectAppGuideData = {};
+let openWindowList = [];
+
+let historyList = ref([])
+
+let appList = ref();
+
+const emits = defineEmits(["changeShowCom"]);
+
+const searchHandler = async () => {
+  let siteTitle = '', favicon = '';
+  
+  if(!siteUrl.value) {
+    return;
+  }
+
+  siteUrl.value = siteUrl.value.trim();
+
+  if(!checkURL(siteUrl.value)) {
+    //提示
+    return;
+  } 
+  const loadingHide = message.loading('loading...', 0);
+  let siteRes = await axios.get(siteUrl.value);
+
+  if(siteRes) {
+    if(siteRes.headers['content-type'].indexOf('text/html') < 0 || siteRes.request.status > 399) {
+      // 提示
+      return;
+    }
+    let urlObj = new URL(siteUrl.value);
+    if(siteRes.data) {
+      siteTitle = getTitleByHtmlStr(siteRes.data);
+      if(!siteTitle) {
+        siteTitle = urlObj.hostname;
+      } 
+      console.log(siteTitle)
+    }
+    favicon = urlObj.origin + '/favicon.ico';
+  } 
+
+  let currentApp = {
+    appId: '',
+    cateId: '',
+    createType: '',
+    defaultUrl: siteUrl.value,
+    guideData: '',
+    iconPath: favicon,
+    interactType: '',
+    linkImagePath: "",
+    name: siteTitle,
+  }
+
+  let convertRes = await convertUrl({params: {originUrl: siteUrl.value}});
+  let params = { convertUrl: siteUrl.value, originUrl: siteUrl.value, appId: '', currentApp };
+
+  loadingHide();
+
+  if(convertRes && convertRes.code == 0) {
+    let {convertUrl} = convertRes.data || {};
+    params.convertUrl = convertUrl;
+  }
+  emits('changeShowCom', params)
+}
+
+const getTitleByHtmlStr = (str) => {
+  let index1 = str.indexOf('<title>') + 7;
+  let index2 = str.indexOf('</title>');
+  return str.substring(index1, index2) || '';
+};
+
+const clickHistoryAppHandler = (params) => {
+  if(params.appId) {
+    clickAppHandler(params);
+  } else {
+    siteUrl.value = params.defaultUrl;
+    searchHandler();
+  }
+};
+
+const clickAppHandler = (params) => {
+  let { createType, defaultUrl, appId, linkImagePath } = params;
+  switch (createType) {
+    case 1:
+      emits('changeShowCom', { convertUrl: defaultUrl, originUrl: defaultUrl, appId, linkImagePath, currentApp: params })
+      break;
+    case 2:
+      openWindow(params);
+      break;
+  }
+}
+
+const openWindow = (params) => {
+  chrome.windows.getCurrent({},
+    function(window) {
+      if(window && window.state == "fullscreen") {
+        chrome.windows.update(window.id,{
+          state: 'normal'
+        }, function() {
+          setTimeout(() => {
+            createGuideWindow(params, true);
+          }, 1000)
+        })
+      } else {
+        createGuideWindow(params);
+      }
+  })
+};
+
+const createGuideWindow = (params, isUpdate = false) => {
+  openWindowList = [];
+  selectAppGuideData = {};
+
+  let windowWith = window.innerWidth - 500;
+  let guideUrl = chrome.runtime.getURL('/iframe/tool-box-guide.html');
+
+  chrome.windows.create({
+    width: windowWith,
+    type: 'popup',
+    url: params.defaultUrl,
+    state: 'normal'
+  }, function (window) {
+    openWindowList.push(window);
+  })
+  chrome.windows.create({
+    width: 500,
+    type: 'popup',
+    url: guideUrl,
+    left: windowWith,
+    state: 'normal'
+  }, function (window) {
+    openWindowList.push(window);
+  })
+
+  if(params.guideData) {
+    selectAppGuideData = JSON.parse(params.guideData);
+  }
+}
+
+const getAppList = () => {
+  getAllPostEditorAppData({params: {}}).then(res => {
+    if(res.code == 0) {
+      appList.value = res.data || [];
+    }
+  })
+}
+
+
+const onRuntimeMsg = () => {
+  chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
+    sendResponse('ok')
+    switch (req.actionType) {
+      case 'CONTENT_GET_GUIDE_DATA':
+        chrome.runtime.sendMessage({ 
+            actionType: "CONTENT_EDIT_SEND_GUIDE_DATA", 
+            data: {
+              guideData: selectAppGuideData,
+              windowData: openWindowList
+            }
+        },(response) => {});
+        break;
+      case 'CONTENT_GUIDE_APPLY_APP':
+        siteUrl.value = req.data.siteUrl;
+        searchHandler();
+        break;
+    }
+  })
+}
+
+const getHistoryList = async () => {
+  let {list = []} = await getChromeStorage('toolBoxAppHistoryData') || {};
+  historyList.value = list;
+};
+
+const imgOnError = (e) => {
+  let img = e.srcElement;
+  img.src = require('@/assets/img/icon-default-app-logo.png');
+  img.onerror = null;
+}
+
+onMounted(() => {
+  getHistoryList();
+  getAppList();
+  onRuntimeMsg();
+})
+</script>
+
+<style lang="scss" scoped>
+.editor-wrapper {
+  width: 100%;
+  height: 100%;
+
+  .top {
+    padding: 25px 40px 30px 40px;
+    border-bottom: 0.5px solid #D1D9DD;
+    box-sizing: border-box;
+
+    .title {
+      font-weight: 500;
+      font-size: 20px;
+    }
+
+    .search-wrapper {
+      margin: 20px 0;
+      border-radius: 8px;
+      width: 100%;
+      height: 49px;
+      display: flex;
+      align-items: center;
+
+      .input {
+        background: #F1F3F4;
+        border: none;
+        outline: none;
+        font-weight: 400;
+        font-size: 16px;
+        color: #636363;
+        height: 100%;
+        width: calc(100% - 49px);
+        padding-left: 20px;
+        border-bottom-left-radius: 8px;
+        border-top-left-radius: 8px;
+      }
+
+      .btn {
+        width: 49px;
+        height: 49px;
+        background: #1D9BF0;
+        border-bottom-right-radius: 8px;
+        border-top-right-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+      }
+    }
+
+    .desc {
+      img {
+        width: 315px;
+        height: 14px;
+        object-fit: cover;
+      }
+    }
+  }
+
+  .bottom {
+    width: 100%;
+    height: calc(100% - 185px);
+    overflow-y: auto;
+
+    .content {
+      width: 100%;
+      padding: 36px 30px 20px 50px;
+      box-sizing: border-box;
+
+      .history-wrapper {
+
+        .app-list {
+            .img-wrapper {
+              width: 60px;
+              height: 60px;
+              border-radius: 10px;
+              margin-bottom: 10px;
+              border: 1px solid #E5E5E5;
+              box-sizing: border-box;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+
+              .img {
+                width: 100%;
+                height: 100%;
+                border-radius: 10px;
+              }
+
+              .small-img {
+                width: 25px;
+                height: 25px;
+              }
+            }
+        }
+      }
+
+      .cate-item {
+        min-height: 110px;
+        display: flex;
+        margin-bottom: 12px;
+
+        .cate {
+          width: 20px;
+          height: 110px;
+          margin-right: 26px;
+          display: flex;
+          align-items: center;
+          margin-top: -10px;
+
+          img {
+            width: 20px;
+            height: 20px;
+          }
+        }
+
+        .app-list {
+          display: flex;
+          align-content: center;
+          flex-wrap: wrap;
+
+          .app {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            width: 110px;
+            height: 110px;
+            cursor: pointer;
+
+            .app-img {
+              width: 60px;
+              height: 60px;
+              border-radius: 10px;
+              margin-bottom: 10px;
+              border: 1px solid #E5E5E5;
+              box-sizing: border-box;
+            }
+
+            .name {
+              font-weight: 500;
+              font-size: 12px;
+              color: #636363;
+              width: 100%;
+              height: 15px;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+              overflow: hidden;
+              text-align: center;
+              box-sizing: border-box;
+              padding: 0 5px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 172 - 0
src/view/iframe/publish/tool-box/child/guide.vue

@@ -0,0 +1,172 @@
+<template>
+    <div class="guide-wrapper">
+        <div class="top">
+            <div class="title">Guide</div>
+            <div class="content">
+                <div class="img-list" 
+                    v-if="pageData.guideData.guideType == 'image'">
+                    <div class="img-item" 
+                        v-for="(item, index) in pageData.guideData.guideData"
+                        :key="index">
+                        <img class="img" :src="item" >
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="bottom">
+            <div class="title">
+                Enter Room Link
+            </div>
+            <div class="search-input-wrapper">
+                <input class="input" v-model="siteUrl" placeholder="Enter link" />
+                <div class="btn" @click="confirm">
+                    <img :src="require('@/assets/svg/icon-tool-box-guide-arrow.svg')" alt="">
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { checkURL } from "@/uilts/help"
+
+let siteUrl = ref('');
+
+let pageData = reactive({
+    guideType: '',
+    guideData: []
+});
+
+const confirm = () => {
+    siteUrl.value = siteUrl.value.trim();
+
+    if(!checkURL(siteUrl.value)) {
+        return;
+    } 
+
+    chrome.runtime.sendMessage({ 
+        actionType: "CONTENT_GUIDE_APPLY_APP", 
+        data: {
+            siteUrl: siteUrl.value
+        }
+    },(response) => {});
+
+    setTimeout(() => {
+        for(let i = 0; i < pageData.windowData.length; i++) {
+            let item = pageData.windowData[i];
+            chrome.windows.remove(
+                item.id,
+                function() {}
+            )
+        };
+    }, 600)
+};
+
+const onRuntimeMsg = () => {
+    chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
+        sendResponse('ok')
+        switch (req.actionType) {
+            case 'CONTENT_EDIT_SEND_GUIDE_DATA':
+                let {guideData, windowData} = req.data;
+                pageData.guideData = guideData;
+                pageData.windowData = windowData;
+                break;
+        }
+    })
+}
+
+onMounted(() => {
+    setTimeout(() => {
+        chrome.runtime.sendMessage({ 
+            actionType: "CONTENT_GET_GUIDE_DATA", 
+            data:{}
+        },(response) => {});
+    }, 600)
+
+    onRuntimeMsg();
+})
+
+</script>
+
+<style lang="scss">
+
+html, body, #app {
+    margin: 0 !important;
+    width: 100%;
+    height: 100%;
+}
+.guide-wrapper {
+    width: 100%;
+    height: 100%;
+
+    .top {
+        height: calc(100% - 158px);
+        padding: 30px 43px;
+        box-sizing: border-box;
+        overflow-y: auto;
+
+        .title {
+            font-weight: 500;
+            font-size: 30px;
+            margin-top: 10px;
+            margin-bottom: 30px;
+        }
+
+        .img-item {
+            margin-bottom: 18px;
+
+            .img {
+                width: 100%;
+            }
+        }
+    }
+
+    .bottom {
+        height: 158px;
+        background: #1D9BF0;
+        padding: 20px;
+        box-sizing: border-box;
+
+        .title {
+            font-weight: 500;
+            font-size: 20px;
+            color: #fff;
+            margin-top: 7px;
+            margin-bottom: 18px;
+        }
+
+        .search-input-wrapper {
+            width: 100%;
+            height: 49px;
+            background: #fff;
+            border-radius: 100px;
+            box-sizing: border-box;
+            display: flex;
+
+            .input {
+                border: none;
+                outline: none;
+                border-radius: 100px;
+                width: calc(100% - 49px);
+                height: 100%;
+                box-sizing: border-box;
+                color: #636363;
+                padding: 15px 20px 15px 20px;
+            }
+
+            .btn {
+                width: 49px;
+                height: 100%;
+                background: #D2EAFC;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                border-top-right-radius: 100px;
+                border-bottom-right-radius: 100px;
+                cursor: pointer;
+            }
+        }
+    }
+}
+</style>

+ 391 - 0
src/view/iframe/publish/tool-box/child/preview.vue

@@ -0,0 +1,391 @@
+<template>
+    <div class="editor-preview-wrapper">
+        <div class="top">
+            <div class="card-container">
+                <!-- 安装之后的卡片样式 -->
+                <div class="content-after" v-show="installStatus" :style="{ 'width': reviewCanvasParams.width + 'px' }">
+                    <div class="head" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img :src="userInfo.avatarUrl" class="avatar" />
+                        <div class="article-wrapper">
+                            <div class="nickname">
+                                {{ userInfo.name }}
+                            </div>
+                            <div class="name">
+                                @{{ userInfo.nickName }}
+                            </div>
+                        </div>
+                    </div>
+                    <div class="after-cover-wrapper" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <install-card :pre_view="true" :iframe_url="previewData.convertUrl"></install-card>
+                    </div>
+                </div>
+
+                <!-- 安装之前的卡片样式 -->
+                <div class="content-before" v-show="!installStatus"
+                    :style="{ 'width': reviewCanvasParams.width + 'px' }">
+                    <div class="head" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img :src="userInfo.avatarUrl" class="avatar" />
+                        <div class="article-wrapper">
+                            <div class="nickname">
+                                {{ userInfo.name }}
+                            </div>
+                            <div class="name">
+                                @{{ userInfo.nickName }}
+                            </div>
+                        </div>
+                    </div>
+                    <div class="card-wrapper" :style="{ 'zoom': reviewCanvasParams.zoom }">
+                        <img class="cover" :src="previewData.linkImagePath || require('@/assets/svg/img-default-site-cover.svg')" />
+                        <!-- v-if="previewData.linkImagePath && previewData.appId" <iframe class="iframe" 
+                            :src="previewData.convertUrl"
+                            v-else></iframe>  -->
+
+                        <div class="bottom-bar">
+                            <div class="site-url">DeNet.me</div>
+                            <div class="desc">Install DeNet Plugin to Participate</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="font">
+                Preview: <span>{{ installStatus ? 'After' : 'Before' }}</span> DeNet Installed
+            </div>
+        </div>
+        <div class="bottom">
+            <div class="btn" @click="publishHandler">NEXT</div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, defineEmits, reactive, defineProps, onMounted, nextTick, onUnmounted } from "vue";
+import { postPublish } from "@/http/publishApi";
+import installCard from '@/view/content/tool-box/index.vue'
+
+import { getChromeStorage, setChromeStorage } from "@/uilts/chromeExtension"
+import { getUser } from "@/http/publishApi"
+
+let installStatus = ref(false);
+
+let userInfo = ref({});
+let submitIng = ref(false);
+
+let reviewCanvasParams = reactive({
+    width: 620,
+    zoom: 1
+});
+let timer = ref(null);
+
+const props = defineProps({
+    previewData: {
+        type: Object,
+        default: {
+            convertUrl: '',
+            originUrl: '',
+            appId: ''
+        }
+    }
+})
+
+const emits = defineEmits(["publishFinish"]);
+
+const getUserInfo = (cb) => {
+    getChromeStorage('userInfo', (res) => {
+        if (res) {
+            userInfo.value = res;
+        }
+        cb && cb(res);
+    })
+}
+
+const getUserName = (screenName) => {
+    getUser({
+        params: {
+            screenName
+        }
+    }).then(res => {
+        console.log(res);
+        if (res.code == 0) {
+            userInfo.value.name = res.data.name || ''
+        }
+    });
+}
+
+const calcPreviewCanvasParams = () => {
+    nextTick(() => {
+        let containerDom = document.querySelector('.card-container');
+        let domHeight = containerDom && containerDom.offsetHeight || 500;
+        const canvasHeight = 780, canvasWidth = 620;
+        if (domHeight < canvasHeight) {
+            //比例: 高 / 宽
+            let hWRatio = canvasHeight / canvasWidth;
+            //缩小宽度 = 高度 / 比例  
+            let width = domHeight / hWRatio;
+            if (width > canvasWidth) {
+                width = canvasWidth;
+            }
+            //缩小比例 
+            let zoom = width / canvasWidth;
+            if (zoom > 1) {
+                zoom = 1;
+            }
+            reviewCanvasParams.width = width;
+            reviewCanvasParams.zoom = zoom;
+        } else {
+            reviewCanvasParams.width = canvasWidth;
+            reviewCanvasParams.zoom = 1;
+        }
+    });
+}
+
+const publishHandler = () => {
+    if(submitIng.value) {
+        return;
+    }
+    let {convertUrl, originUrl, appId, currentApp} = props.previewData;
+
+    setHistoryData(currentApp);
+
+    let postBizData = {
+        convertUrl,
+        originUrl,
+        appId
+    };
+    let data = {
+        params: {
+            postBizData: JSON.stringify(postBizData),
+            postSrc: 1, // 1 twitter
+            postType: 3, //3 Tool box
+        },
+    };
+
+    submitIng.value = true;
+    
+    postPublish(data).then((res) => {
+        submitIng.value = false;
+        if (res.code == 0) {
+            let publishRes = res.data;
+            emits("publishFinish", { publishRes });
+        } else {
+        }
+    })
+    .catch((err) => {
+        console.log(err);
+    });
+}
+
+const setHistoryData = async (params) => {
+    const maxLength = 9;
+    let {list = []} = await getChromeStorage('toolBoxAppHistoryData') || {};
+    if(list.length) {
+        list.unshift(params);
+        if(list.length > maxLength) {
+            list.length = maxLength;
+        }
+        setChromeStorage({ toolBoxAppHistoryData : JSON.stringify({
+            list: list
+        })}) 
+    } else {
+        setChromeStorage({ toolBoxAppHistoryData : JSON.stringify({
+            list: [params]
+        })})    
+    }
+};
+
+onMounted(() => {
+    calcPreviewCanvasParams();
+    getUserInfo((res) => {
+        if (res) {
+            getUserName(res.nickName);
+        }
+        clearInterval(timer.value);
+        timer.value = setInterval(() => {
+            installStatus.value = !installStatus.value;
+        }, 3000)
+    });
+    window.addEventListener('resize', function () {
+        calcPreviewCanvasParams();
+    })
+})
+
+onUnmounted(() => {
+    clearInterval(timer.value);
+})
+
+</script>
+
+<style lang="scss" scoped>
+.editor-preview-wrapper {
+    width: 100%;
+    height: 100%;
+
+    .top {
+        width: 100%;
+        height: calc(100% - 80px);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        overflow-y: auto;
+        padding: 20px 0;
+        box-sizing: border-box;
+
+        .card-container {
+            height: 100%;
+            margin-right: 50px;
+
+            .content-after,
+            .content-before {
+                position: relative;
+            }
+
+            .head {
+                position: absolute;
+                z-index: 1100;
+                top: 20px;
+                left: 17px;
+                display: flex;
+
+                .avatar {
+                    width: 47px;
+                    height: 47px;
+                    border-radius: 50%;
+                    object-fit: cover;
+                    margin-right: 13px;
+                }
+
+                .article-wrapper {
+                    display: flex;
+
+                    .nickname {
+                        font-weight: 500;
+                    }
+
+                    .nickname,
+                    .name {
+                        font-size: 15px;
+                    }
+                    .name {
+                        color: #566370;
+                        margin-left: 7px;
+                    }
+                }
+            }
+
+            .content-after {
+                background: url('@/assets/img/img-tool-box-preview-after.png');
+                width: 387px;
+                height: 100%;
+                background-size: contain;
+                background-repeat: no-repeat;
+                border: 1px solid #D1D9DD;
+                border-radius: 13px;
+                box-sizing: border-box;
+
+                .after-cover-wrapper {
+                    position: absolute;
+                    z-index: 100;
+                    top: 108px;
+                    left: 78px;
+                    width: 425px;
+                    height: 458px;
+                    border-radius: 10px;
+                    // border: 1px solid #D1D9DD;
+                }
+
+            }
+
+            .content-before {
+                background: url('@/assets/img/img-tool-box-preview-before.png');
+                background-size: contain;
+                background-repeat: no-repeat;
+                height: 100%;
+                border: 1px solid #D1D9DD;
+                border-radius: 13px;
+                box-sizing: border-box;
+
+                .card-wrapper {
+                    width: 505px;
+                    height: 338px;
+                    border: 1px solid #D1D9DD;
+                    background: #ffffff;
+                    box-sizing: border-box;
+                    overflow: hidden;
+                    position: relative;
+                    box-sizing: border-box;
+                    border-radius: 16px;
+                    left: 73px;
+                    top: 90px;
+
+                    .iframe {
+                        height: calc(100% - 73px);
+                        width: 100%;
+                        border: none;    
+                        pointer-events: none;
+                        cursor: default;
+                    }
+
+                    .cover {
+                        height: calc(100% - 73px);
+                        width: 100%;
+                        object-fit: contain;
+                    }
+
+                    .bottom-bar {
+                        width: 100%;
+                        height: 73px;
+                        padding: 10px 10px 0 13px;
+                        border-top: 1px solid rgba(0, 0, 0, 0.3);
+                        .site-url {
+                            color: #566370;
+                            font-size: 14px;
+                            line-height: 20px;
+                        }
+                        .desc {
+                            font-weight: 500;
+                            font-size: 15px;
+                            line-height: 21px;
+                            font-weight: 500;
+                        }
+                    }
+                }
+            }
+        }
+
+        .font {
+            width: 300px;
+            font-weight: 600;
+            font-size: 20px;
+
+            span {
+                color: #1D9BF0;
+            }
+        }
+    }
+
+    .bottom {
+        width: 100%;
+        height: 80px;
+        box-shadow: 0px -1px 0px #ECECEC;
+        box-sizing: border-box;
+        border-bottom-left-radius: 16px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        .btn {
+            width: 200px;
+            height: 50px;
+            background: #1D9BF0;
+            border-radius: 10000px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-weight: 700;
+            font-size: 18px;
+            color: #fff;
+            margin-right: 30px;
+            cursor: pointer;
+        }
+    }
+
+}
+</style>

+ 70 - 0
src/view/iframe/publish/tool-box/index.vue

@@ -0,0 +1,70 @@
+<template>
+    <div class="page-wrapper">
+        <editor v-show="showCom == 'EDITOR'" :linkInputDescImage="pageData.linkInputDescImage" @changeShowCom="changeShowCom" />  
+        <preview v-show="showCom == 'PREVIEW'" :previewData="previewData" 
+        @publishFinish="publishFinish" />
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, defineProps, defineEmits } from "vue";
+
+import editor from '@/view/iframe/publish/tool-box/child/editor.vue'
+import preview from '@/view/iframe/publish/tool-box/child/preview.vue'
+
+const props = defineProps({
+    pageData: {
+        type: Object,
+        default: {
+            linkInputDescImage: ''
+        }
+    },
+    activePage: {
+        type: String,
+        default: 'EDITOR', // EDITOR PREVIEW
+    },
+});
+
+const emits = defineEmits(["onToolBoxPageChange"]);
+
+watch(
+    () => props.activePage,
+    (newVal) => {
+        showCom.value = newVal;
+    },
+    {
+        deep: true
+    }
+);
+
+let showCom = ref('EDITOR'); 
+let previewData = reactive({
+    convertUrl: '',
+    originUrl: '',
+    appId: '',
+    currentApp: {}
+})
+
+const changeShowCom = (params) => {
+    showCom.value = 'PREVIEW';
+    previewData.convertUrl = params.convertUrl;
+    previewData.originUrl = params.originUrl;
+    previewData.appId = params.appId
+    previewData.linkImagePath = params.linkImagePath || '';
+    previewData.currentApp = params.currentApp || {};
+
+    emits("onPageChange", {page: showCom.value});
+}
+
+const publishFinish = (params) => {
+    emits("toolBoxPublishFinish", params);
+}
+
+</script>
+
+<style lang="scss" scoped>
+    .page-wrapper {
+        width: 100%;
+        height: 100%;
+    }
+</style>

+ 65 - 27
src/view/iframe/tab-group/tab-group.vue

@@ -41,14 +41,24 @@
                                     </template>
                                 </el-popover>
                             </div>
-                            <div class="nick-name">
+                            <div class="nick-name"
+                                :style="{
+                                    color: eleThemeStyle.color
+                                }">
                                 {{item.nickName}}
                             </div>
-                            <div class="screen-name">
+                            <div class="screen-name"
+                                :style="{
+                                    color: eleThemeStyle.screenName
+                                }">
                                 @{{item.screenName}}
                             </div>
                         </div>
-                        <div class="post-content" v-html="item.textContent"></div>
+                        <div class="post-content"
+                            :style="{
+                                color: eleThemeStyle.color
+                            }"
+                            v-html="item.textContent"></div>
                     </div>
                 </div>
             </template>
@@ -61,7 +71,7 @@
 </template>
 
 <script setup>
-import { onMounted, ref } from "vue";
+import { onMounted, reactive, ref } from "vue";
 import { getGroupPostList, getTwitterNftGroupInfo } from '@/http/nft'
 import { getQueryString } from '@/uilts/help.js'
 import { ElPopover } from "element-plus";
@@ -75,6 +85,12 @@ let listWrapperDom = ref(null);
 let pageWrapperDom = ref(null);
 let loading = ref(false);
 
+let eleThemeStyle = reactive({
+    color: '#000',
+    screenName: '#566370',
+    borderColor: '#F0F3F4',
+})
+
 let listReqParams = {
     params: {
         pageSize: 100,
@@ -110,6 +126,9 @@ function onRuntimeMsg() {
             case 'CONTENT_SEND_GROUP_NAV_TOP':
                 styleHandler(req.data);
                 break;
+            case 'CONTENT_SYS_THEME_CHANGE':
+                setPageThemeStyle(req.data);
+                break;
         }
     })
 }
@@ -234,6 +253,22 @@ const initData = () => {
     }
 }
 
+const setPageThemeStyle = (params) => {
+    let {twitterTheme, theme} = params;
+
+    if(twitterTheme == 'light') {
+        eleThemeStyle.color = '#000';
+        eleThemeStyle.screenName = '#566370';
+        eleThemeStyle.borderColor = '#F0F3F4';
+        document.querySelector('body').style.backgroundColor = '#fff'
+    } else if (twitterTheme == 'dark'){
+        eleThemeStyle.color = '#fff';
+        eleThemeStyle.screenName = '#fff';
+        eleThemeStyle.borderColor = '#000';
+        document.querySelector('body').style.backgroundColor = '#000'
+    }
+};
+
 onMounted(() => {
     onRuntimeMsg();
     initData();
@@ -259,28 +294,31 @@ html, body, #app {
     display: none !important;
 }
 
-@media (prefers-color-scheme: light) {
-    body {
-        background: #fff;
-    }
-}
-
-@media (prefers-color-scheme: dark) {
-    .list-item {
-        border-bottom: 1px solid #000 !important;
-    }
-    .nick-name {
-        color: #fff !important;
-    }
-
-    .screen-name {
-        color: #fff !important;
-    }
-
-    .post-content {
-        color: #fff !important;
-    }
-}
+// @media (prefers-color-scheme: light) {
+//     body {
+//         background: #fff;
+//     }
+// }
+
+// @media (prefers-color-scheme: dark) {
+//     body {
+//         background: #000 !important;
+//     }
+//     .list-item {
+//         border-bottom: 1px solid #000 !important;
+//     }
+//     .nick-name {
+//         color: #fff !important;
+//     }
+
+//     .screen-name {
+//         color: #fff !important;
+//     }
+
+//     .post-content {
+//         color: #fff !important;
+//     }
+// }
 
 .preview-nft {
     box-sizing: border-box;
@@ -399,7 +437,7 @@ html, body, #app {
                     font-weight: 400;
                     font-size: 16px;
                     line-height: 24px;
-                    color: rgb(15, 20, 25);
+                    color: #000;
                     font-family: TwitterChirp, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                     word-break: break-all;
                     white-space: pre-line;

+ 265 - 0
src/view/iframe/test/index.vue

@@ -0,0 +1,265 @@
+<template>
+    <div class="denet-toolbox" @click.stop="clickHead">
+        <div class="head" @click.stop="clickHead">
+            <span></span>
+            <div>
+                <img :src="require('@/assets/img/icon-fixed.png')" alt class="fixed" @click.stop="clickFixed" />
+                <img :src="require('@/assets/img/icon-full.png')" alt class="full" @click.stop="clickFull" />
+                <!-- <img :src="require('@/assets/img/icon-close.png')" alt class="full" @click="clickFull" /> -->
+            </div>
+        </div>
+        <div class="content">
+            <iframe src="https://www.bilibili.com" v-show="state.status == ''" frameborder="0"></iframe>
+            <!-- 网页错误 -->
+            <div class="state" v-show="state.status == '网页错误'">
+                <img :src="require('@/assets/img/icon-page-fail.png')" alt />
+                <div>Oops, this link is invalid</div>
+            </div>
+
+            <!-- 加载 -->
+            <div class="state" v-show="state.status == '加载'">
+                <img :src="require('@/assets/img/icon-loading-gray.png')" alt />
+            </div>
+
+            <!-- 关闭 -->
+            <div class="state" v-show="state.status == '关闭'">
+
+            </div>
+            <!-- 固定右上角 -->
+            <div class="state" v-show="state.status == '固定右上角'">
+                <img :src="require('@/assets/img/icon-page-fail.png')" alt />
+                <div>Pinned to the top right</div>
+            </div>
+        </div>
+        <!-- alert -->
+        <div class="alert" v-show="state.show_alert">
+            <div class="back" @click.stop="clickCancel"></div>
+            <div class="confirm">
+                <div class="check">
+                    <input :id="state.checkbox_id" type='checkbox' v-model="state.checkbox" />
+                    <label :for="state.checkbox_id">Don't remind</label>
+                </div>
+
+                <div class="title">Web Page Progress May Reset</div>
+                <div class="handle">
+                    <div class="cancel" @click.stop="clickCancel">Cancel</div>
+                    <div class="continue" @click.stop="clickContinue">Continue</div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script setup>
+import { getChromeStorage, setChromeStorage, httpContentToBack } from "@/uilts/chromeExtension";
+import { guid } from "@/uilts/help";
+import { onMounted, reactive } from "vue";
+
+let state = reactive({
+    status: '', //
+    show_alert: false,
+    list: [],
+    checkbox: false,
+    checkbox_id: `denet-${guid()}`
+})
+onMounted(() => {
+   
+})
+
+const clickHead = () => {
+    // 
+}
+
+const clickContinue = () => {
+    if (state.checkbox) {
+        setChromeStorage({ fullCheck: JSON.stringify({ fullCheck: 1 }) })
+        // 全屏
+    }
+}
+
+const clickCancel = () => {
+    state.show_alert = false
+}
+
+const clickFixed = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 固定
+
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+const clickFull = () => {
+    getChromeStorage('fullCheck', (res) => {
+        if (res && res.fullCheck) {
+            // 全屏
+
+        } else {
+            state.show_alert = true
+        }
+    })
+}
+
+
+</script>
+
+<style lang="scss">
+.denet-toolbox {
+    width: 505px;
+    height: 544px;
+    filter: drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.2));
+    border-radius: 12px;
+    overflow: hidden;
+    position: relative;
+
+    .alert {
+        text-align: center;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+
+        .back {
+            background: #000000;
+            opacity: 0.8;
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            cursor: auto;
+        }
+
+        .confirm {
+            position: absolute;
+            width: 355px;
+            height: 180px;
+            background: #FFFFFF;
+            border-radius: 20px;
+            top: 173px;
+            left: 50%;
+            margin-left: -180px;
+
+            .title {
+                font-weight: 600;
+                font-size: 18px;
+                color: #000000;
+                margin-bottom: 34px;
+            }
+
+            .check {
+                color: #899099;
+                font-weight: 400;
+                font-size: 14px;
+                margin: 12px 15px 32px 0;
+                text-align: right;
+                align-content: center;
+                justify-content: flex-end;
+                display: flex;
+                line-height: 17px;
+
+                input {
+                    margin-right: 8px;
+                }
+
+                label {
+                    line-height: 19px;
+                }
+
+            }
+
+            .handle {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+
+                div {
+                    font-weight: 600;
+                    font-size: 16px;
+                    width: 156px;
+                    height: 47px;
+                    line-height: 47px;
+                    cursor: pointer;
+                    border-radius: 1000px;
+                    user-select: none;
+                }
+
+                .cancel {
+                    color: #000000;
+                    background: rgba(56, 154, 255, 0.01);
+                    border: 1px solid #E6E6E6;
+                }
+
+                .continue {
+                    background: #1D9BF0;
+                    font-weight: 600;
+                    margin-left: 11px;
+                    color: #FFFFFF;
+                }
+            }
+        }
+    }
+
+    .head {
+        width: 100%;
+        height: 40px;
+        background: #373737;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        span {
+            color: #FFFFFF;
+            font-style: normal;
+            font-weight: 500;
+            font-size: 14px;
+            margin-left: 16px;
+        }
+
+        img {
+            width: 20px;
+            height: 20px;
+            cursor: pointer;
+
+        }
+
+        .full {
+            margin-right: 16px;
+        }
+
+        .fixed {
+            margin-right: 20px;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 40px);
+        background: #686868;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        iframe {
+            width: 100%;
+            height: 100%;
+        }
+
+        .state {
+            img {
+                margin-bottom: 14px;
+            }
+
+            div {
+                color: #8E8E8E;
+                text-align: center;
+                font-weight: 500;
+                font-size: 22px;
+            }
+        }
+    }
+}
+</style>

+ 4 - 4
vue.config.js

@@ -100,10 +100,10 @@ module.exports = {
             from: path.resolve(`src/manifest.json`),
             to: `${path.resolve('dist')}/manifest.json`
           },
-          // {
-          //   from: path.resolve(`src/rules`),
-          //   to: `${path.resolve('dist')}/rules`
-          // }
+          {
+            from: path.resolve(`src/rules`),
+            to: `${path.resolve('dist')}/rules`
+          }
         ]
       })
     ],

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott