瑞强 吴 hai 8 meses
pai
achega
01064b7fa9
Modificáronse 5 ficheiros con 862 adicións e 40 borrados
  1. 619 0
      lib/astcss/cssRefers.js
  2. 0 0
      lib/astcss/csstree.js
  3. 7 2
      page.css
  4. 22 6
      page.html
  5. 214 32
      page.js

+ 619 - 0
lib/astcss/cssRefers.js

@@ -0,0 +1,619 @@
+class CssPriority {
+    static _TOKEN_ID = /(#[\w-]+)/g
+    static _TOKEN_CLASS = /(\.[\w-]+)/g
+    static _TOKEN_ATTR = /(\[[^[\]]+])/g
+    static _TOKEN_PSEUDO_CLASS = /(:[\w-]+)/g
+    static _TOKEN_PSEUDO_ELEM = /(::[\w-]+)/g
+    static _TOKEN_ELEM = /([\w-]+)/g
+
+    static _PSEUDO_ELEMS = [
+        'first-letter',
+        'last-letter',
+        'first-line',
+        'last-line',
+        'first-child',
+        'last-child',
+        'before',
+        'after'
+    ]
+
+    static _SYMBOL_B = '\x02'
+    static _SYMBOL_C = '\x03'
+    static _SYMBOL_D = '\x04'
+
+    static compare(a, b) {
+        //在sort应用中,返回<0,则a在b之前,>0则a在b之后
+        for (let i = 0; i < a.length; i++) {
+            if (a[i] !== b[i]) {
+                //return a[i] - b[i]
+                return b[i] - a[i];
+            }
+        }
+
+        return 0
+    }
+
+    /**
+     * parse then priority of selector (CSS Rule)
+     * @param  {String} selector css selector
+     * @param  {Object} opts
+     *  - {Boolean} opts.important is current rule important
+     *  - {Number} opts.line line number of css rule
+     * @return {Array} array that contains 6 priority number
+     */
+    static parse(selector, opts) {
+        opts = opts || {}
+
+        // priority: [ important, style, R1, R2, R3, line ]
+        let important = opts.important ? 1 : 0
+        let line = opts.line || 0
+        let priority = [important, 0, 0, 0, 0, line]
+
+        selector = selector.replace(/(::?)([\w-]+)/g, (total, left, elem) => {
+            if (CssPriority._PSEUDO_ELEMS.indexOf(elem) >= 0) {
+                if (left === ':') {
+                    return '::' + elem
+                } else {
+                    return total
+                }
+            } else {
+                return total
+            }
+        })
+
+        // replace with symbols
+        selector = selector
+            .replace(CssPriority._TOKEN_ATTR, CssPriority._SYMBOL_C)
+            .replace(CssPriority._TOKEN_PSEUDO_ELEM, CssPriority._SYMBOL_D)
+            .replace(CssPriority._TOKEN_PSEUDO_CLASS, CssPriority._SYMBOL_C)
+            .replace(CssPriority._TOKEN_ID, CssPriority._SYMBOL_B)
+            .replace(CssPriority._TOKEN_CLASS, CssPriority._SYMBOL_C)
+            .replace(CssPriority._TOKEN_ELEM, CssPriority._SYMBOL_D)
+
+        // count
+        selector = selector.replace(/[\2\3\4]/g, (symbol) => {
+            let idx = symbol.charCodeAt(0)
+
+            priority[idx]++
+
+            return '<' + idx + '>'
+        })
+
+        return priority
+    }
+ 
+}
+
+
+class Lock {
+    constructor() {
+        this.promise = new Promise((resolve, reject) => {
+            this.resolve = resolve;
+            this.reject = reject;
+        });
+    }
+
+    async wait() {
+        try {
+            let result = await this.promise;
+            return result;
+        } catch (error) {
+            throw error;
+        }
+    }
+}
+
+class CssKind {
+    static inline = 1;
+    static match = 2;
+    static parent = 3;
+}
+
+
+class CssRefers {
+    _document;
+    _cssTree;
+    _cssAst = [];
+
+    _inheritStyles = new Map([["color", null], ["font", null], ["font-family", null], ["font-size", null], ["font-style", null], ["font-weight", null], ["font-height", null], ["font-variant", null], ["font-stretch", null], ["font-size-adjust", null],
+        ["letter-spacing", null], ["word-spacing", null], ["text-align", null], ["text-indent", null], ["text-decoration", null], ["white-space", null], ["text-shadow", null], ["text-transform", null], ["direction", null],
+        ["caption-side", null], ["empty-cells", null], ["border-collapse", null],
+        ["unicode-bidi", null], ["visibility", null], ["cursor", null], ["user-select", null], ["list-style", null], ["list-style-type", null], ["list-style-position", null], ["list-style-image", null], ["display", null], ["columns", null], ["column-count", null], ["column-gap", null], ["column-rule", null], ["column-span", null], ["column-width", null],
+        ["break-before", null], ["break-after", null], ["break-inside", null], ["line-height", null], ["cursor", null], ["quotes", null]
+    ]);
+
+    _cssCount = 0;
+    constructor(doc, cssTree) {
+        this._document = doc;
+        this._cssTree = cssTree;
+    }
+
+    async #initCssAst() {        
+        if (this._cssTree) {
+            if (this._cssAst.length > 0) return;
+
+            for (let i = 0; i < this._document.styleSheets.length; i++) {
+                let styleSheet = this._document.styleSheets[i];
+
+                if (styleSheet.href) {
+                    let cssCode = await this.#getCssData(styleSheet.href);
+
+                    if (cssCode) {
+                        let ast = this.#cssCodeToAST(cssCode);
+
+                        this._cssAst.push({
+                            file: styleSheet.href,
+                            ast: ast,
+                            code:cssCode,
+                        });
+                    }
+                }
+            }
+        }
+    }
+
+    async #getCssData(file) {
+        let response = await fetch(file);
+        return await response.text();
+    }
+
+    #cssCodeToAST(cssCode) {
+        let curAst = this._cssTree.parse(cssCode, {
+            parseAtrulePrelude: false,
+            parseRulePrelude: false,
+            parseValue: false,
+            positions: true,
+
+        });
+
+        let selectors = [];
+
+        //https://github.com/csstree/csstree/blob/master/docs/parsing.md#parsesource-options
+        csstree.walk(curAst, (node) => {
+            if (node.type === 'Rule')//node.type === 'Declaration'
+            {
+                let curSelector;
+                if (node.prelude.children) {
+                    curSelector = node.prelude.children.map(child => child.raw);
+                }
+                else {
+                    curSelector = node.prelude.value;
+                }
+
+                if (curSelector) selectors.push({
+                    name: curSelector.replaceAll("\r\n", " ") ,//curSelector,
+                    loc: node.prelude.loc,
+                });
+
+            }
+        });
+
+        return selectors;
+    }
+
+    //获取元素内联样式定义
+    #getInlineStyle(ele) {
+        let ret = [];
+
+        for (let i = 0; i < ele.style.length; i++) {
+            let sn = ele.style[i];
+
+            let isImportant = false;
+
+            if ((ele.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
+                isImportant = true;
+            }
+
+            ret.push({
+                name: sn,
+                cssText: ele.style[sn],
+                isImportant: isImportant,
+            });
+        }
+
+        if (ret.length <= 0) {
+            return ret;
+        }
+        else {
+            this._cssCount = _cssCount + 1;
+            return [{
+                selectorName: "",
+                declarations: "",
+                href: "",
+                style: ret,
+                id: `c_${this._cssCount}`,
+                fileIndex: null,
+                posIndex:null,
+            }];
+        }
+    }
+
+    //计算选择器的权重级别
+    #getSelectorPriority(selector,opts) {
+        //github.com/yibn2008/css-priority
+        return CssPriority.parse(selector,opts);
+    }
+
+    //获取当前元素的匹配样式
+    #getMatchStyle(ele) {
+        if (!ele) return [];
+
+        const matchedCSSRules = [];
+
+        // 获取所有样式表
+        for (let i = 0; i < this._document.styleSheets.length; i++) {
+            const styleSheet = this._document.styleSheets[i];
+            try {
+                // 遍历每个样式表的规则
+                for (let j = 0; j < styleSheet.cssRules.length; j++) {
+                    const rule = styleSheet.cssRules[j];
+
+                    let isMatch = false;
+                    if (rule.selectorText == ":root") {
+                        if (ele.tagName == "HTML") isMatch = true;                        
+                    }
+                    else if (rule.selectorText == ":link" || rule.selectorText == ":visited") {
+                        if (ele.tagName == "A") isMatch = true;                        
+                    }
+                    else if (rule.selectorText == ":disabled" || rule.selectorText == ":enabled") {
+                        if (["INPUT", "TEXTAREA","SELECT","BUTTON","FIELDSET"].indexOf(ele.tagName) >= 0) isMatch = true;
+                    }
+                    else {
+                        if (ele.matches(rule.selectorText)) {
+                            isMatch = true;
+                        }
+                    }
+
+                    if (isMatch) {
+
+                        let styles = [];
+                        if (rule.style) {
+                            let sn = "";
+                            for (let i = 0; i < rule.style.length; i++) {
+                                sn = rule.style[i];
+
+                                let isImportant = false;
+
+                                if ((rule.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
+                                    isImportant = true;
+                                }
+
+                                styles.push({
+                                    name: sn,
+                                    cssText: rule.style[sn],
+                                    isImportant: isImportant,
+                                });
+                            }
+                        }
+
+                        this._cssCount = this._cssCount + 1;
+                        let loc = null;
+
+                        if (this._cssAst.length > 0) {
+                            let fileAst = this._cssAst.find((x) => x.file == styleSheet.href);
+                            if (fileAst) {
+                                let selectorAst = fileAst.ast.find((x) => x.name == rule.selectorText);
+                                if (selectorAst) loc = selectorAst.loc;
+                            }
+                        }
+
+
+                        matchedCSSRules.push({
+                            selectorName: rule.selectorText,
+                            declarations: rule.style,
+                            href: styleSheet.href || '',
+                            style: styles,
+                            id: `c_${this._cssCount}`,
+                            //priority: this.#getSelectorPriority(rule.selectorText, {line:1000000-(i * 1000000 + j)}),
+                            priority: this.#getSelectorPriority(rule.selectorText, { line: i * 1000000 + j}),
+                            fileIndex: i,
+                            posIndex: j,
+                            loc:loc,
+                        });
+                    }
+                }
+            } catch (error) {
+                console.error('无法访问样式表', error);
+            }
+        }                
+
+        //根据优先级排序
+        return matchedCSSRules.sort((a, b) => {
+            return CssPriority.compare(a.priority, b.priority);
+        });;
+    }
+
+    //获取父级样式
+    #getParentStyle(ele) {
+        let styleList = [];
+        let level = 0;
+
+        let pEle = ele.parentElement;
+        while (pEle) {
+            let inlineStyle = this.#getInlineStyle(pEle);
+            if (inlineStyle.length > 0) {
+                styleList.push({
+                    relationEle: pEle,
+                    relationTag: pEle.tagName,
+                    nodeLevel: level,
+                    kind: CssKind.inline,
+                    selector: inlineStyle,
+                });
+
+                level = level + 1;
+            }
+
+            let matchStyle = this.#getMatchStyle(pEle);
+            if (matchStyle.length > 0) {
+                styleList.push({
+                    relationEle: pEle,
+                    relationTag: pEle.tagName,
+                    nodeLevel: level,
+                    kind: CssKind.match,
+                    selector: matchStyle,
+                });
+
+                level = level + 1;
+            }
+
+            pEle = pEle.parentElement;
+            /*
+            if (pEle.tagName == "HTML") {
+                pEle = null;
+            }*/
+        }
+
+        return styleList;
+    }
+
+    #getTopSelector(ele, selector,selectorName,line) {
+        let find = null;
+        for (let gItem of selectorName.split(",")) {
+            if (!ele.matches(gItem)) continue;
+
+            if (!find) {
+                find = {
+                    selector: selector,
+                    key: gItem,
+                    priority: this.#getSelectorPriority(gItem, {line:line}),
+                }
+            }
+            else {
+                let curPriority = this.#getSelectorPriority(gItem, { line: line });
+                let ret = CssPriority.compare(find.priority, curPriority);
+                if (ret > 0) {
+                    find.selector = selector;
+                    find.key = gItem;
+                    find.priority = curPriority;
+                }
+            }
+        }
+
+        return find;
+    }
+
+    async #getDefaultStyle(eleTag) {
+        let tmpF = this._document.tmpF;
+        let w = new Lock();
+        let tmpFStyle = null;
+
+        if (!tmpF) {
+            tmpF = this._document.createElement("iframe");
+            tmpF.style.display = "none";
+            tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
+
+            this._document.body.appendChild(tmpF);
+
+            this._document.tmpF = tmpF;
+
+            tmpF.onload = function () {
+                tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
+                w.resolve(tmpFStyle);
+            }
+
+        }
+        else {
+            tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
+
+            tmpF.onload = function () {
+                tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
+                w.resolve(tmpFStyle);
+            }
+        }
+
+        return await w.wait();
+    }
+
+    //获取非默认样式
+    async #getComputedStyleProp(ele) {
+        let ret = [];
+
+        //获取默认样式
+        let defaultStyle = await this.#getDefaultStyle(ele.tagName);
+
+        //获取应用样式
+        let computeStyle = getComputedStyle(ele);
+
+        for (let i = 0; i < computeStyle.length - 1; i++) {
+            let sn = computeStyle[i];
+            if (computeStyle[sn] == defaultStyle[sn]) continue;
+
+            ret.push({
+                name: sn,
+                cssText: computeStyle[sn],
+            })
+        }
+        
+        return ret;
+    }
+
+    //获取css的ast
+    async getAst() {
+        await this.#initCssAst();
+
+        return this._cssAst();
+    }
+
+    async refers(ele, propName) {
+        let ret = [];
+
+        //初始化css的ast
+        this.#initCssAst();
+
+        let inlineStyle = this.#getInlineStyle(ele);//获取自身的样式配置
+        let matchStyle = this.#getMatchStyle(ele);//获取匹配的样式配置
+        let parentStyle = this.#getParentStyle(ele);//获取所有父级的样式配置
+
+        let computeStyleProps = await this.#getComputedStyleProp(ele);//获取浏览器的非默认样式属性
+
+        
+
+        //插入内联样式
+        if (inlineStyle.length > 0) {
+            ret.push({
+                kind: CssKind.inline,
+                selector: inlineStyle.selector,
+                parent: null,
+            })
+        }
+
+        //插入匹配样式
+        for (let matchItem of matchStyle) {
+            ret.push({
+                kind: CssKind.match,
+                selector: matchItem,
+                parent:null,
+            })
+        }
+
+        let inheritedRelation = [];
+        //分析继承样式        
+        for (let sty of computeStyleProps) {
+            if (!this._inheritStyles.has(sty.name)) continue;//非继承属性,则不需要判断父级
+            if (propName && sty.name != propName) continue;
+
+            //遍历多级父级
+            for (let psty of parentStyle) {                
+                if (psty.selector?.length <= 0) continue;
+                //遍历父级中的选择器
+                for (let selectorItem of psty.selector) {
+                    if (selectorItem.style.length <= 0) continue;
+                    let findProp = false;
+                    //遍历选择器的样式属性
+                    for (let pro of selectorItem.style) {
+                        if (pro.name == sty.name) {
+                            findProp = true;
+                            break;
+                        }
+                    }
+                    //找到了对应属性
+                    if (findProp) {
+                        let findSelector = inheritedRelation.find((x) => x.selector.id == selectorItem.id);
+                        if (!findSelector) {
+                            //选择器不存在,则插入
+                            inheritedRelation.push({
+                                kind: CssKind.parent,
+                                selector: selectorItem,
+                                inheritedInfo: psty,
+                            });
+                        }
+                        continue;
+                    }
+                }
+            }
+        }
+
+        //计算继承的优先顺序
+        inheritedRelation = inheritedRelation.sort((a, b) => {
+            if (a.inheritedInfo.nodeLevel < b.inheritedInfo.nodeLevel) {
+                return -1;
+            }
+            else if (a.inheritedInfo.nodeLevel > b.inheritedInfo.nodeLevel) {
+                return 1;
+            }
+            else {
+                return 0
+            }
+        });
+
+        ret = ret.concat(inheritedRelation);
+
+        let propApplyRelation = [];
+
+        //计算属性的匹配位置
+        for (let prop of computeStyleProps) {
+            if (propName && prop.name != propName) continue;
+
+            let findSelector = null;             
+            let isImportant = false;
+
+            //遍历关联的选择器            
+            for (let selector of ret) {
+                //{kind:"",selector:{},parent:null }
+
+                let isFindProp = false;
+                //遍历样式属性
+                for (let sty of selector.selector.style) {
+                    if (prop.name == sty.name) {
+                        isFindProp = true;
+                        //判断是否配置了important申明
+                        if (sty.isImportant) {
+                            isImportant = true;
+                        }
+
+                        break;
+                    }
+                }
+
+                //找到了对应的属性
+                if (isFindProp) {
+                    if (!findSelector) {
+                        findSelector = selector;
+                    }
+                    else {
+                        if (selector.kind != CssKind.parent) {
+                            //important>inline>match -parent继承类中的important不会应用到子级元素
+                            if (isImportant) {
+                                //发现important定义则结束当前属性所在选择器的遍历
+                                findSelector = selector;
+                                break;
+                            }
+                        }
+                        else {
+                            //优先级inline>match>parent
+                            if (findSelector.kind < selector.kind) continue;
+                        }
+
+                        //处理kind相等的情况
+                        if (findSelector.selector.selectorName.indexOf(",") > 0 || selector.selector.selectorName.indexOf(",") > 0) {
+                            //判断逗号之间的选择器的优先级
+                            let findSelectorTop = this.#getTopSelector(ele, findSelector.selector, findSelector.selector.selectorName, findSelector.selector.priority[5]);
+                            let backupSelectorTop = this.#getTopSelector(ele, selector.selector, selector.selector.selectorName, selector.selector.priority[5]);
+
+                            let curCompare = CssPriority.compare(findSelectorTop.priority, backupSelectorTop.priority);
+                            if (curCompare > 0) {
+                                findSelector = selector;
+                            }
+                        }
+
+                    }
+                }
+            }
+
+            if (findSelector) {
+                propApplyRelation.push({
+                    name: prop.name,
+                    cssText: prop.cssText,
+                    selector: findSelector,
+                }
+                );
+            }
+        }
+
+        return {
+            applyRelation: propApplyRelation,
+            applySelectors: ret,
+        };
+    }
+
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
lib/astcss/csstree.js


+ 7 - 2
page.css

@@ -15,7 +15,7 @@
 
 .zl-table-height {
     /* height: calc(50vh - 17px); */
-    height: calc(50vh - 68px);
+    height: calc(50vh - 40px);
     overflow: auto;
 }
 
@@ -186,7 +186,7 @@ body {
 }
 
 .tab-content {
-    padding: 20px;
+    padding: 15px;
     border: 1px solid #ccc;
     border-radius: 5px;
     margin-top: -2px;
@@ -210,4 +210,9 @@ body {
 
 div#jscode {
     height: calc(100vh - 108px);
+}
+
+.table-title{
+    position: absolute;
+    z-index: 100;
 }

+ 22 - 6
page.html

@@ -19,6 +19,8 @@
     <script src="./lib/zlExpressEditor/monaco-editor/min/vs/loader.min.js"></script>
     <script src="./js/CodeEditor.js"></script>
 
+    <script src="./lib/astcss/csstree.js"></script>
+
     <script src="./lib/ast/astparser.js"></script>
     <script src="./js/AstHelper.js"></script>
     <script src="page.js"></script>
@@ -28,15 +30,16 @@
 <body>
     <div class="zl-container">
         <div class="tabs">
-            <div class="tab active" data-id="tabFirst">元素</div>
+            <div class="tab active" data-id="tabFirst">选择器调用清单</div>
             <div class="tab" data-id="tabSecond">事件</div>
+            <!-- <div class="tab" data-id="tabThree"></div> -->
         </div>
         <div class="tab-container">
             <div class="tab-content active" id="tabFirst">
                 <div class="zl-row">
                     <div class="zl-col-6">
                         <div class="zl-row">
-                            <div class="zl-table-head">JS中过滤器清单</div>
+                            <div class="zl-table-head table-title">JS中过滤器清单</div>
                             <div class="zl-table-height">
                                 <div class="table table-bordered" id="filterTable">
                                 </div>
@@ -49,9 +52,9 @@
                                 </div>
                             </div>
                         </div> -->
-                        <div class="zl-row">
-                            <div class="zl-table-head">样式属性</div>
-                            <div class="zl-code">
+                        <div class="zl-row" style="padding-top: 10px;">
+                            <div class="zl-table-head table-title">样式属性</div>
+                            <div class="zl-table-height">
                                 <div class="table table-bordered" id="cssJson">
                                 </div>
                             </div>
@@ -60,7 +63,7 @@
                     <div class="zl-col-6">
                         <div class="zl-row">
                             <div class="zl-col-12">
-                                <div class="zl-table-head">JS实现代码</div>
+                                <div class="zl-table-head">实现代码</div>
                                 <div class="zl-code" id="jscode"></div>
                             </div>
                         </div>
@@ -74,6 +77,19 @@
                 </div>
             </div>
             <div class="tab-content" id="tabSecond">
+                <div class="zl-row">
+                    <div class="zl-col-12">
+                        <div class="zl-row">
+                            <div class="zl-table-head table-title">JS事件</div>
+                            <div class="zl-table-height">
+                                <div class="table table-bordered" id="eventTable">
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="tab-content" id="tabThree">
                 <div class="zl-row">
                     <div class="zl-col-12">
                         <div class="zl-row">

+ 214 - 32
page.js

@@ -5,12 +5,17 @@ let nodeObj;
 let jslist;
 let cssList;
 let cssFileInfo = {}
+let oldFile; // 记录上一次点击的文件信息
+
+// 记录页卡信息
+// const pageTab = new PageTab();
 
 chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
   if (request.msgToPopup === "发送页面") {
     cssFileInfo = {}
     //初始化JS代码编辑器
     window.jsCode = new CodeEditor();
+
     jsCode.InitEditor($("#jscode")[0], false, false, [], false);
     jsCode.dataType = 3;
     InitTable();
@@ -73,7 +78,7 @@ function InitTable() {
         visible: true,
         highlightCaseSensitive: true,
       },
-      groupPanel: { visible: true },
+      groupPanel: { visible: false },
       grouping: {
         allowCollapsing: true,
         autoExpandAll: true,
@@ -90,20 +95,11 @@ function InitTable() {
         {
           dataField: "JS文件",
           dataType: "JS文件",
-          groupIndex: 0,
         },
         {
           dataField: "过滤器",
           caption: "过滤器",
         },
-        {
-          dataField: "选择器类型",
-          dataType: "选择器类型",
-        },
-        {
-          dataField: "调用方式",
-          dataType: "调用方式",
-        },
         {
           dataField: "所在位置",
           dataType: "所在位置",
@@ -160,7 +156,7 @@ function InitTable() {
       selection: {
         mode: "single",
       },
-      groupPanel: { visible: true },
+      groupPanel: { visible: false },
       grouping: {
         allowCollapsing: true,
         autoExpandAll: true,
@@ -180,17 +176,20 @@ function InitTable() {
           caption: "来源文件",
           cellTemplate: function (tdom, tdMsg) {
             let name = tdMsg.value;
-            tdom.text(name === "用户自定义" ? name : name.match(/\/([^\/?]+)(?:\?[^\s]*)?$/)[1])
+            try {
+              name = (name === "用户自定义" ? name : name.match(/\/([^\/]+\.css)(\?[^ ]*)?$/)[1])
+            } catch {
+            }
+            tdom.text(name)
           }
         },
         {
-          dataField: "选择器",
-          caption: "选择器"
+          dataField: "分类",
+          caption: "分类"
         },
         {
-          dataField: "分类",
-          caption: "分类",
-          groupIndex: 0
+          dataField: "选择器",
+          caption: "选择器"
         }, {
           dataField: "样式属性",
           caption: "样式属性",
@@ -203,7 +202,6 @@ function InitTable() {
       ],
       onSelectionChanged: async function (selectedItems) {
         let data = selectedItems.selectedRowsData[0];
-        jsCode.dataType = 1;
         if (data) {
           if (data.文件) {
             let fileName = data.文件;
@@ -211,11 +209,42 @@ function InitTable() {
             if (!cssFileInfo[fileName]) {
               code = fileName === "用户自定义" ? "" : await (await fetch(fileName)).text();
             } else {
-              code = !cssFileInfo[fileName]
+              code = cssFileInfo[fileName].code
+            }
+            // 设置显示css
+            jsCode.dataType = 2;
+            if (oldFile !== fileName) {
+              //更改JS代码
+              jsCode.SetCode(code);
+              cssFileInfo[fileName] = {
+                code: jsCode.GetCode()
+              }
+            }
+            //移出所有高亮
+            jsCode.RemoveAllHighlight();
+
+            oldFile = fileName;
+            // 定位使用
+            let selectors = [];
+            if (!cssFileInfo[fileName].selectors) {
+              selectors = await cssCodeToAST(jsCode.GetCode());
+              cssFileInfo[fileName].selectors = selectors
+            } else {
+              selectors = cssFileInfo[fileName].selectors
+            }
+            let location = selectors.find(a => a.name === data.选择器);
+            if (location) {
+              setTimeout(() => {
+                let { loc: { start, end } } = location;
+                //设置高亮
+                jsCode.CreateHighlight({
+                  startLine: start?.line,
+                  startColumn: start?.column,
+                  endLine: end?.line,
+                  endColumn: end?.column,
+                });
+              }, 500);
             }
-            cssFileInfo[fileName] = code
-            //更改JS代码
-            jsCode.SetCode(code);
           }
         }
       },
@@ -278,6 +307,43 @@ function findPropRef(key, json) {
   return retArr;
 }
 
+async function cssCodeToAST(cssCode) {
+  let curAst = csstree.parse(cssCode, {
+    parseAtrulePrelude: false,
+    parseRulePrelude: false,
+    parseValue: false,
+    positions: true,
+
+  });
+
+  let selectors = [];
+
+  //https://github.com/csstree/csstree/blob/master/docs/parsing.md#parsesource-options
+  await csstree.walk(curAst, (node) => {
+    if (node.type === 'Rule')//node.type === 'Declaration'
+    {
+      let curSelector;
+      if (node.prelude.children) {
+        curSelector = node.prelude.children.map(child => child.raw);
+      }
+      else {
+        curSelector = node.prelude.value;
+      }
+      let loc = node.prelude.loc;
+
+      if (curSelector) selectors.push({
+        //name: curSelector,//curSelector,
+        name: curSelector.replaceAll("\r\n*", " ").replaceAll("\r\n", " "),
+        sourceName: cssCode.substring(loc.start.offset, loc.end.offset),
+        loc: loc,
+      });
+
+    }
+  });
+
+  return selectors;
+}
+
 async function getPathNodeInfo(path) {
   const doc = await getDocument();
   // const htmlInfo = doc.children.find((m) => m.localName === "html");
@@ -504,7 +570,7 @@ function CssJsonData(jsonData) {
   });
   if (内敛的样式.length > 0) {
     result.push({
-      文件: "",
+      文件: "用户自定义",
       样式属性: 内敛的样式,
       选择器: "",
       分类: "内敛样式"
@@ -795,9 +861,10 @@ class CssJsonRules {
         let sameInfo = CssJsonRules.findExtentInfo(shorthandEntries);
         style = style.filter(s => !sameInfo.includes(s.name))
       }
+      let text = rule.selectorList.text;
       let obj = {
-        origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义',
-        text: rule.selectorList.text,
+        origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
+        text,
         style
       };
       result.匹配的样式.push(obj)
@@ -807,11 +874,14 @@ class CssJsonRules {
       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 }))
-        }))
+        matchs: matches.map(({ rule }) => {
+          let text = rule.selectorList.text;
+          return {
+            origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
+            text,
+            style: rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }))
+          }
+        })
       };
       result.伪类的样式.push(obj)
     })
@@ -822,9 +892,10 @@ class CssJsonRules {
         let { rule } = cssRule;
         let style = rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }));
         if (style.length === 0) return;
+        let text = rule.selectorList.text;
         result.继承的样式.push({
-          origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义',
-          text: rule.selectorList.text,
+          origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
+          text,
           style
         })
       })
@@ -833,3 +904,114 @@ class CssJsonRules {
     return result;
   }
 }
+
+/**
+ * 页卡组件
+ */
+class PageTab {
+  #tabs = []
+  #tabContainer
+  constructor({ tabs }) {
+    this.#tabs = tabs || [];
+    this.#tabContainer = document.querySelector('.tabs');
+    this.#initClickEvent();
+  }
+
+  addTab(tabInfo) {
+    this.#tabs.push(tabInfo)
+  }
+
+  #initClickEvent() {
+    this.#tabContainer.addEventListener('click', (e) => {
+      let target = e.target;
+      if (target.classList.contains("tab")) {
+        this.activeTabById(target.dataset.id);
+      }
+    })
+  }
+
+  get tabs() {
+    return this.#tabs;
+  }
+
+  activeTabById(id) {
+    // 移除已经选中的
+    let activedTab = this.tabs.find(a => a.isActive);
+    if (activedTab) {
+      activedTab.removeActive();
+    }
+    // 给设置的选中
+    let awaitActiveTab = this.tabs.find(a => a.id === id);
+    if (awaitActiveTab) {
+      awaitActiveTab.addActive();
+    }
+  }
+
+}
+
+class tab {
+  #id // 页卡id
+  #tabEle // tab页卡元素
+  #contentEle // tab内容元素
+  #isAlived // 是否保持持久化(为true表示 页卡激活事件只执行一次,当前内容会一直存在,如果为false,则表示每次点击页卡都会重新执行激活事件)
+  activeEvent // 激活页卡执行事件
+  #isActived // 是否选中
+
+  constructor({ id, isAlived, activeEvent }) {
+    this.#id = id;
+    this.#tabEle = tab.getTabEleById(id);
+    this.#contentEle = tab.getTabContentById(id);
+    this.#isAlived = isAlived;
+    this.#isActived = false;
+    this.activeEvent = activeEvent;
+  }
+
+  get tabInfo() {
+    return {
+      isActive: this.isActive(),
+      id: this.#id
+    }
+  }
+
+  /**
+   * 判断是否为选中的标签
+   */
+  isActive() {
+    return this.#isActived;
+  }
+
+  /**
+   * 添加选中效果
+   */
+  addActive() {
+    this.#tabEle.classList.add('active');
+    this.#contentEle.classList.add('active');
+    this.#isActived = true;
+    this.activeEvent();
+  }
+
+  activeEvent() {
+    if (this.#isAlived === true) {
+      // 如果开启了持久化,表示只执行一次
+      this.activeEvent = function () { }
+    }
+    typeof this.activeEvent === 'function' && this.activeEvent();
+  }
+
+  /**
+   * 移除选中效果
+   */
+  removeActive() {
+    this.#tabEle.classList.remove('active');
+    this.#contentEle.classList.remove('active');
+    this.#isActived = false;
+  }
+
+  static getTabEleById(id) {
+    return document.querySelector(`.tabs tab[data-id="${id}"]`);
+  }
+
+  static getTabContentById(id) {
+    return document.querySelector(`.tab-container #${id}`);
+  }
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio