Selaa lähdekoodia

提交样式功能

瑞强 吴 8 kuukautta sitten
vanhempi
commit
ea6e81a08d
4 muutettua tiedostoa jossa 469 lisäystä ja 129 poistoa
  1. 6 1
      content.js
  2. 12 0
      page.css
  3. 8 8
      page.html
  4. 443 120
      page.js

+ 6 - 1
content.js

@@ -147,7 +147,12 @@ function clickHandler(e) {
     action: "show_popup",
     jslist: jsRequests,
     targe: oldTarget.outerHTML,
-    csslist: myComputedStyle(oldTarget),
+    csslist: Array.from(document.styleSheets).map(({ href, cssRules }) => {
+      return {
+        href,
+        selectorTexts: Array.from(cssRules).map(({ selectorText }) => selectorText)
+      }
+    }),
     path: getNodePosition(oldTarget),
   });
 }

+ 12 - 0
page.css

@@ -198,4 +198,16 @@ body {
 .tab-content.active {
     display: block;
     /* 仅显示激活的内容 */
+}
+#cssJson li {
+    list-style: none;
+}
+
+#cssJson ul {
+    padding: 0;
+    margin: 0;
+}
+
+div#jscode {
+    height: calc(100vh - 108px);
 }

+ 8 - 8
page.html

@@ -42,12 +42,19 @@
                                 </div>
                             </div>
                         </div>
-                        <div class="zl-row">
+                        <!-- <div class="zl-row">
                             <div class="zl-table-head">样式属性</div>
                             <div class="zl-table-height">
                                 <div class="table table-bordered" id="cssJson">
                                 </div>
                             </div>
+                        </div> -->
+                        <div class="zl-row">
+                            <div class="zl-table-head">样式属性</div>
+                            <div class="zl-code">
+                                <div class="table table-bordered" id="cssJson">
+                                </div>
+                            </div>
                         </div>
                     </div>
                     <div class="zl-col-6">
@@ -63,13 +70,6 @@
                                 <div class="zl-tree" id="astJson"></div>
                             </div>
                         </div> -->
-                        <div class="zl-row">
-                            <div class="zl-table-head">属性设置来源</div>
-                            <div class="zl-code">
-                                <div class="table table-bordered" id="cssBelong">
-                                </div>
-                            </div>
-                        </div>
                     </div>
                 </div>
             </div>

+ 443 - 120
page.js

@@ -3,6 +3,64 @@ let jsonInfo = {};
 let tabId = "";
 let nodeObj;
 let jslist;
+let cssList;
+
+chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
+  if (request.msgToPopup === "发送页面") {
+    //初始化JS代码编辑器
+    window.jsCode = new CodeEditor();
+    jsCode.InitEditor($("#jscode")[0], false, false, [], false);
+    jsCode.dataType = 3;
+    InitTable();
+
+    // 结构接收的信息
+    let { jslist: _jslist, targe, csslist, tabId: _tabId, path } = request.data;
+
+    jslist = _jslist;
+    tabId = _tabId;
+    cssList = csslist; // 页面的所有样式信息
+
+    parseCodeToAST(
+      jslist.map((item) => {
+        return {
+          code: item.data,
+          name: item.fileName,
+        };
+      })
+    );
+
+    let data = window.findSelector();
+    let id = $(targe).attr("id");
+    let classList = $(targe).attr("class").split(" ");
+    data = data.filter((m) => {
+      let v = m.过滤器;
+      if (v.includes(id)) return true;
+
+      if (classList.length > 0) {
+        for (const classitem of classList) {
+          if (v.includes(classitem)) return true;
+        }
+      }
+    });
+
+    await attach();
+    await getPathNodeInfo(path);
+    let events = await findEvent();
+
+    TabInstance.option({
+      dataSource: data,
+    });
+    let cssJsonData = CssJsonData(jsonInfo)
+    CSSTabInstance.option({
+      dataSource: cssJsonData,
+    });
+
+    eventTableInstance.option({
+      dataSource: translateEventData(events),
+    });
+  }
+});
+
 function InitTable() {
   window.TabInstance = $("#filterTable")
     .dxDataGrid({
@@ -53,9 +111,10 @@ function InitTable() {
           dataType: "DOM操作分类",
         },
       ],
-      onContentReady(e) {},
+      onContentReady(e) { },
       onSelectionChanged: function (selectedItems) {
         let data = selectedItems.selectedRowsData[0];
+        jsCode.dataType = 3;
         if (data) {
           let ast = data.astJSON;
           if (data.JS文件 !== nowFileName) {
@@ -63,8 +122,6 @@ function InitTable() {
             let code = window.fileList.find((o) => o.name === data.JS文件).code;
             //更改JS代码
             jsCode.SetCode(code);
-            //更改ast
-            //window.ShowASTTree(ast, document.getElementById('astJson'))
           }
           //jscode定位
           let {
@@ -84,16 +141,6 @@ function InitTable() {
               endColumn: end?.column + 1,
             });
           }
-          {
-            // //ast定位
-            // let jsonNode = document.getElementById(iid);
-            // if (jsonNode) {
-            //     let textNode = Array.from(jsonNode.parentNode.childNodes).find(item => item.classList.contains("node-text"));
-            //     $("#astJson").find(".monaco-highlight").removeClass("monaco-highlight");
-            //     textNode?.classList?.add("monaco-highlight");
-            //     scrollTo(jsonNode)
-            // }
-          }
         }
       },
     })
@@ -111,6 +158,12 @@ function InitTable() {
       selection: {
         mode: "single",
       },
+      groupPanel: { visible: true },
+      grouping: {
+        allowCollapsing: true,
+        autoExpandAll: true,
+        expandMode: "rowClick",
+      },
       allowColumnReordering: true,
       rowAlternationEnabled: true,
       showBorders: true,
@@ -121,66 +174,42 @@ function InitTable() {
       },
       columns: [
         {
-          dataField: "key",
-          caption: "名称",
+          dataField: "文件",
+          caption: "来源文件"
         },
         {
-          dataField: "value",
-          caption: "生效值",
+          dataField: "选择器",
+          caption: "选择器"
+        },
+        {
+          dataField: "分类",
+          caption: "分类",
+          groupIndex: 0
+        }, {
+          dataField: "样式属性",
+          caption: "样式属性",
+          cellTemplate: function (tdom, tdMsg) {
+            let styles = tdMsg.value;
+            let html = styles.map(({ name, value }) => `<li>${name}: ${value}</li>`).join('')
+            tdom.html(`<ul>${html}</ul>`)
+          }
         },
       ],
-      onSelectionChanged: function (selectedItems) {
+      onSelectionChanged: async function (selectedItems) {
         let data = selectedItems.selectedRowsData[0];
+        jsCode.dataType = 1;
         if (data) {
-          CSSBelongInstance.option({
-            dataSource: findPropRef(data.key, jsonInfo).map((s, i) => ({
-              ...s,
-              id: i,
-            })),
-          });
+          if (data.文件) {
+            let fileName = data.文件;
+            let code = fileName === "用户自定义" ? "" : await (await fetch(fileName)).text();
+            //更改JS代码
+            jsCode.SetCode(code);
+          }
         }
       },
     })
     .dxDataGrid("instance");
 
-  window.CSSBelongInstance = $("#cssBelong")
-    .dxDataGrid({
-      dataSource: [],
-      keyExpr: "id",
-      remoteOperations: false,
-      searchPanel: {
-        visible: true,
-        highlightCaseSensitive: true,
-      },
-      selection: {
-        mode: "single",
-      },
-      allowColumnReordering: true,
-      rowAlternationEnabled: true,
-      showBorders: true,
-      paging: {
-        enabled: false,
-        pageSize: 0,
-      },
-      height: "100%",
-      columns: [
-        {
-          dataField: "来源",
-          caption: "来源",
-          width: 100,
-        },
-        {
-          dataField: "选择器",
-          caption: "选择器",
-        },
-        {
-          dataField: "属性值",
-          caption: "属性值",
-        },
-      ],
-    })
-    .dxDataGrid("instance");
-
   window.eventTableInstance = $("#eventTable")
     .dxDataGrid({
       dataSource: [],
@@ -218,61 +247,6 @@ function InitTable() {
     })
     .dxDataGrid("instance");
 }
-chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
-  if (request.msgToPopup === "发送页面") {
-    //初始化JS代码编辑器
-    window.jsCode = new CodeEditor();
-    jsCode.InitEditor($("#jscode")[0], false, false, [], false);
-    jsCode.dataType = 3;
-    InitTable();
-    let ret = request.data;
-    console.log(ret);
-    let { jslist: _jslist, targe, csslist, tabId: _tabId, path } = ret;
-    jslist = _jslist;
-    tabId = _tabId;
-    parseCodeToAST(
-      jslist.map((item) => {
-        return {
-          code: item.data,
-          name: item.fileName,
-        };
-      })
-    );
-
-    let data = window.findSelector();
-    let id = $(targe).attr("id");
-    let classList = $(targe).attr("class").split(" ");
-    data = data.filter((m) => {
-      let v = m.过滤器;
-      if (v.includes(id)) return true;
-
-      if (classList.length > 0) {
-        for (const classitem of classList) {
-          if (v.includes(classitem)) return true;
-        }
-      }
-    });
-
-    await attach();
-    await getPathNodeInfo(path);
-    let events = await findEvent();
-    TabInstance.option({
-      dataSource: data,
-    });
-
-    CSSTabInstance.option({
-      dataSource: Object.entries(csslist).map((a, index) => ({
-        id: index,
-        key: a[0],
-        value: a[1],
-      })),
-    });
-
-    eventTableInstance.option({
-      dataSource: translateEventData(events),
-    });
-  }
-});
 
 function findPropRef(key, json) {
   let matchedCSSRules = json.matchedCSSRules;
@@ -336,7 +310,7 @@ async function attach() {
   //   } else {
   //     await chrome.debugger.attach({ tabId }, "1.3");
   //   }
-  await chrome.debugger.attach({ tabId }, "1.3").catch(() => {});
+  await chrome.debugger.attach({ tabId }, "1.3").catch(() => { });
   await chrome.debugger.sendCommand({ tabId }, "DOM.enable");
   await chrome.debugger.sendCommand({ tabId }, "CSS.enable");
   await chrome.debugger.sendCommand({ tabId }, "Debugger.enable");
@@ -425,7 +399,6 @@ async function listenerGetFile(listener) {
   );
   let js = jslist.find((m) => m.data == ret.scriptSource);
   if (js) {
-    debugger;
     js.scriptId = listener.scriptId;
     listener.fileName = js.fileName;
     listener.fileUrl = js.url;
@@ -434,7 +407,6 @@ async function listenerGetFile(listener) {
 }
 
 function translateEventData(eventObj) {
-  debugger;
   let { winEvents, documentEvents, selfEvents } = eventObj;
   let result = [];
   result.push(
@@ -498,3 +470,354 @@ function showContent(tab) {
     curActiveContent.classList.add("active");
   }
 }
+
+/**
+ * 接收转换后有用的json数据
+ * @param {} jsonData 
+ */
+function CssJsonData(jsonData) {
+  let data = CssJsonRules.parseJson(jsonData);
+  let { 伪类的样式, 内敛的样式, 匹配的样式, 继承的样式 } = data;
+  let result = [];
+  伪类的样式.forEach((rule) => {
+    let { matchs, pseudoType } = rule;
+    matchs.forEach(match => {
+      result.push({
+        文件: match.origin,
+        样式属性: match.style,
+        选择器: match.text,
+        分类: "伪类" + pseudoType + "样式"
+      })
+    })
+  });
+  if (内敛的样式.length > 0) {
+    result.push({
+      文件: "",
+      样式属性: 内敛的样式,
+      选择器: "",
+      分类: "内敛样式"
+    })
+  }
+  匹配的样式.forEach((rule) => {
+    result.push({
+      文件: rule.origin,
+      样式属性: rule.style,
+      选择器: rule.text,
+      分类: "匹配样式"
+    })
+  })
+
+  继承的样式.forEach((rule) => {
+    result.push({
+      文件: rule.origin,
+      样式属性: rule.style,
+      选择器: rule.text,
+      分类: "继承样式"
+    })
+  })
+  return result.map((a, index) => ({ ...a, id: index }));
+}
+
+
+/**
+ * css基础权重规则
+ */
+class CSSSRule {
+  static calculateSpecificity(selector) {
+    // 初始化权重
+    let ids = 0;
+    let classes = 0;
+    let tags = 0;
+
+    // 去掉多余的空格
+    const parts = selector.trim().split(/\s+/);
+
+    parts.forEach(part => {
+      // 检查每个选择器部分
+      if (part.startsWith('#')) {
+        ids += 1; // ID选择器
+      } else if (part.startsWith('.')) {
+        classes += 1; // 类选择器
+      } else {
+        tags += 1; // 标签选择器
+      }
+    });
+
+    // 返回权重对象
+    // 计算最终权重值
+    const specificityValue = (ids * 100) + (classes * 10) + tags;
+    return specificityValue;
+  }
+
+  static cacheStyle = {}
+
+  static getStyleHref(styleId, selectorText) {
+    if (CSSSRule.cacheStyle[styleId]) return CSSSRule.cacheStyle[styleId];
+    foo: for (let sheet of cssList) {
+      try {
+        let { href, selectorTexts } = sheet;
+        for (let text of selectorTexts)
+          if (selectorText === text) {
+            CSSSRule.cacheStyle[styleId] = href;
+            break foo;
+          }
+      } catch { }
+    }
+    return CSSSRule.cacheStyle[styleId]
+  }
+
+  // css属性扩展
+  static cssExtent = [
+    {
+      "property": "margin",
+      "sub-properties": [
+        "margin-top",
+        "margin-right",
+        "margin-bottom",
+        "margin-left"
+      ]
+    },
+    {
+      "property": "padding",
+      "sub-properties": [
+        "padding-top",
+        "padding-right",
+        "padding-bottom",
+        "padding-left"
+      ]
+    },
+    {
+      "property": "border",
+      "sub-properties": [
+        "border-width",
+        "border-style",
+        "border-color",
+        "border-top",
+        "border-right",
+        "border-bottom",
+        "border-left"
+      ]
+    },
+    {
+      "property": "border-style",
+      "sub-properties": [
+        "border-top-style",
+        "border-right-style",
+        "border-bottom-style",
+        "border-left-style"
+      ]
+    },
+    {
+      "property": "border-top",
+      "sub-properties": [
+        "border-top-width",
+        "border-top-style",
+        "border-top-color"
+      ]
+    },
+    {
+      "property": "border-bottom",
+      "sub-properties": [
+        "border-bottom-width",
+        "border-bottom-style",
+        "border-bottom-color"
+      ]
+    },
+    {
+      "property": "border-left",
+      "sub-properties": [
+        "border-left-width",
+        "border-left-style",
+        "border-left-color"
+      ]
+    },
+    {
+      "property": "border-right",
+      "sub-properties": [
+        "border-right-width",
+        "border-right-style",
+        "border-right-color"
+      ]
+    },
+    {
+      "property": "border-radius",
+      "sub-properties": [
+        "border-top-left-radius",
+        "border-top-right-radius",
+        "border-bottom-right-radius",
+        "border-bottom-left-radius"
+      ]
+    },
+    {
+      "property": "background",
+      "sub-properties": [
+        "background-color",
+        "background-image",
+        "background-repeat",
+        "background-position",
+        "background-size",
+        "background-attachment",
+        "background-clip",
+        "background-origin"
+      ]
+    },
+    {
+      "property": "font",
+      "sub-properties": [
+        "font-style",
+        "font-variant",
+        "font-weight",
+        "font-size",
+        "line-height",
+        "font-family"
+      ]
+    },
+    {
+      "property": "list-style",
+      "sub-properties": [
+        "list-style-type",
+        "list-style-position",
+        "list-style-image"
+      ]
+    },
+    {
+      "property": "text-decoration",
+      "sub-properties": [
+        "text-decoration-line",
+        "text-decoration-color",
+        "text-decoration-style",
+        "text-decoration-thickness"
+      ]
+    },
+    {
+      "property": "transition",
+      "sub-properties": [
+        "transition-property",
+        "transition-duration",
+        "transition-timing-function",
+        "transition-delay"
+      ]
+    },
+    {
+      "property": "animation",
+      "sub-properties": [
+        "animation-name",
+        "animation-duration",
+        "animation-timing-function",
+        "animation-delay",
+        "animation-iteration-count",
+        "animation-direction",
+        "animation-fill-mode",
+        "animation-play-state"
+      ]
+    },
+    {
+      "property": "grid",
+      "sub-properties": [
+        "grid-template-rows",
+        "grid-template-columns",
+        "grid-template-areas",
+        "grid-area",
+        "grid-column",
+        "grid-row",
+        "grid-auto-rows",
+        "grid-auto-columns",
+        "grid-auto-flow"
+      ]
+    },
+    {
+      "property": "flex",
+      "sub-properties": [
+        "flex-grow",
+        "flex-shrink",
+        "flex-basis"
+      ]
+    },
+    {
+      "property": "outline",
+      "sub-properties": [
+        "outline-width",
+        "outline-style",
+        "outline-color"
+      ]
+    }
+  ]
+}
+
+/**
+* 根据json导出数据
+*/
+class CssJsonRules {
+  static findExtentInfo(entries) {
+    return entries.map(e => {
+      let extentInfo = CSSSRule.cssExtent.find(a => a.property === e.name);
+      if (extentInfo) {
+        return extentInfo["sub-properties"];
+      }
+      return undefined
+    }).filter(Boolean).flat();
+  }
+
+  /**
+   * 解析json数据
+   * @param {*} jsonData 
+   * @returns 
+   */
+  static parseJson(jsonData) {
+    let { inlineStyle, matchedCSSRules, pseudoElements, inherited } = jsonData;
+    let result = {
+      "内敛的样式": [],
+      "匹配的样式": [],
+      "伪类的样式": [],
+      "继承的样式": []
+    }
+    // 解析json中的内敛样式
+    result.内敛的样式.push(...inlineStyle.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value })));
+    // 解析json中的匹配样式
+    matchedCSSRules.forEach(cssRule => {
+      let { rule } = cssRule;
+      let { shorthandEntries } = rule;
+      // 按照规律查询出信息
+      let style = rule.style.cssProperties.filter(a => a.text !== undefined || rule.origin === "user-agent").map(({ name, value }) => ({ name, value }));
+      if (rule.origin === "user-agent" && shorthandEntries && shorthandEntries.length > 0) {
+        let sameInfo = CssJsonRules.findExtentInfo(shorthandEntries);
+        style = style.filter(s => !sameInfo.includes(s.name))
+      }
+      let obj = {
+        origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义',
+        text: rule.selectorList.text,
+        style
+      };
+      result.匹配的样式.push(obj)
+    })
+    // 解析伪类的样式
+    pseudoElements.forEach(cssRule => {
+      let { pseudoType, matches } = cssRule;
+      let obj = {
+        pseudoType,
+        matchs: matches.map(({ rule }) => ({
+          origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义',
+          text: rule.selectorList.text,
+          style: rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }))
+        }))
+      };
+      result.伪类的样式.push(obj)
+    })
+    // 解析继承的样式
+    inherited.forEach(inheritedCssRule => {
+      let matchedCSSRules = inheritedCssRule.matchedCSSRules;
+      matchedCSSRules.forEach((cssRule) => {
+        let { rule } = cssRule;
+        let style = rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }));
+        if (style.length === 0) return;
+        result.继承的样式.push({
+          origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义',
+          text: rule.selectorList.text,
+          style
+        })
+      })
+    })
+
+    return result;
+  }
+}