├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── stale.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs └── image.png ├── manifest.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── _locales │ └── en │ │ └── messages.json ├── icon-128.png └── icon-34.png ├── src ├── assets │ ├── fonts │ │ ├── ibm-plex-mono-v19-latin-regular.woff2 │ │ ├── poppins-v20-latin-600.woff2 │ │ └── poppins-v20-latin-regular.woff2 │ ├── img │ │ └── background.png │ └── style │ │ ├── fonts.css │ │ └── theme.css ├── environment.d.ts ├── global.d.ts ├── interfaces │ └── app.interface.ts ├── lib.d.ts ├── lib │ ├── codemirror │ │ ├── css.ts │ │ ├── extensions.ts │ │ ├── lang-css │ │ │ ├── highlight.js │ │ │ ├── index.cjs │ │ │ ├── index.d.cts │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── parser.terms.js │ │ │ └── tokens.js │ │ └── theme.ts │ └── mitt.ts ├── pages │ ├── background │ │ └── index.ts │ ├── components │ │ ├── custom-css │ │ │ ├── CustomCSSElements.vue │ │ │ └── CustomCSSGlobal.vue │ │ ├── dom-navigation │ │ │ └── DomNavigationItem.vue │ │ └── ui │ │ │ ├── UiButton.vue │ │ │ ├── UiCodemirror.vue │ │ │ ├── UiElementProperties.vue │ │ │ ├── UiElementSelector.vue │ │ │ ├── UiElementSpacing.vue │ │ │ ├── UiPopover.vue │ │ │ ├── UiSwitch.vue │ │ │ └── UiTooltip.vue │ └── content │ │ ├── index.ts │ │ └── ui │ │ ├── App.vue │ │ ├── app-plugin.ts │ │ ├── app │ │ ├── AppElementDetail.vue │ │ ├── AppElementScanner.vue │ │ ├── AppToolbar.vue │ │ ├── detail │ │ │ ├── DetailAttributes.vue │ │ │ ├── DetailCSSEditor.vue │ │ │ ├── DetailElementHTML.vue │ │ │ ├── DetailHeader.vue │ │ │ └── DetailStyle.vue │ │ ├── scanner │ │ │ └── ScannerNavigation.vue │ │ └── toolbar │ │ │ ├── ToolbarAssets.vue │ │ │ ├── ToolbarColorPalette.vue │ │ │ ├── ToolbarCustomCSS.vue │ │ │ ├── ToolbarEyeDropper.vue │ │ │ └── ToolbarNavigator.vue │ │ ├── index.ts │ │ ├── injected.css │ │ └── keys.ts ├── storages │ ├── base.ts │ ├── eye-dropper.storage.ts │ └── settings.storage.ts ├── utils │ ├── CSSRulesUtils.ts │ ├── CaptureTab.ts │ ├── RuntimeMessage.ts │ ├── constant.ts │ ├── css-rule-parser.ts │ ├── dom-navigator.ts │ ├── extractColorFromImage.ts │ ├── generate-element-css.ts │ ├── get-applied-css.ts │ ├── getElProperties.ts │ └── helper.ts ├── vite-env.d.ts └── vue-shim.d.ts ├── tailwind.config.js ├── tsconfig.json ├── utils ├── log.ts ├── manifest-parser │ └── index.ts ├── plugins │ ├── add-hmr.ts │ ├── custom-dynamic-import.ts │ ├── make-manifest.ts │ └── watch-rebuild.ts ├── reload │ ├── constant.ts │ ├── initReloadClient.ts │ ├── initReloadServer.ts │ ├── injections │ │ ├── script.ts │ │ └── view.ts │ ├── interpreter │ │ ├── index.ts │ │ └── types.ts │ ├── rollup.config.mjs │ └── utils.ts └── scripts │ └── build-zip.js └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:import/recommended", 11 | "plugin:vue/vue3-recommended", 12 | "plugin:prettier/recommended" 13 | ], 14 | "parser": "vue-eslint-parser", 15 | "parserOptions": { 16 | "parser": "@typescript-eslint/parser" 17 | }, 18 | "plugins": ["@typescript-eslint", "import"], 19 | "settings": { 20 | "import/resolver": { 21 | "typescript": { 22 | "project": "./tsconfig.json" 23 | } 24 | } 25 | }, 26 | "rules": { 27 | "linebreak-style": 0, 28 | "prettier/prettier": [ 29 | "error", 30 | { 31 | "endOfLine": "auto" 32 | } 33 | ] 34 | }, 35 | "globals": { 36 | "chrome": "readonly" 37 | }, 38 | "ignorePatterns": ["watch.js", "dist/**"] 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - OS: [e.g. Mac, Window, Linux] 27 | - Browser [e.g. chrome, firefox] 28 | - Node Version [e.g. 18.12.0] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an Issue or Pull Request becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale Issue or Pull Request is closed 4 | daysUntilClose: 30 5 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking as stale 10 | staleLabel: stale 11 | # Comment to post when marking as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when removing the stale label. Set to `false` to disable 17 | unmarkComment: false 18 | # Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable 19 | closeComment: true 20 | # Limit to only `issues` or `pulls` 21 | only: issues 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # build 8 | /dist 9 | /dist-zip 10 | 11 | # etc 12 | .DS_Store 13 | .env.local 14 | .idea 15 | 16 | # compiled 17 | utils/reload/*.js 18 | utils/reload/injections/*.js 19 | public/manifest.json 20 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm run commitlint ${1} 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm dlx lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=@testing-library/dom 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.12.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .gitignore 4 | .github 5 | .eslintignore 6 | .husky 7 | .nvmrc 8 | .prettierignore 9 | LICENSE 10 | *.md 11 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "trailingComma": "all", 4 | "semi": true, 5 | "singleQuote": true, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ahmad Kholid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Inspect CSS 5 | The easiest way to inspect and edit CSS 6 | 7 | 8 | 9 | ## Features 10 | - 🔎 Get CSS Properties from any element by selecting it 11 | - ✏️ Get and edit element attributes 12 | - 📷 Download website assets 13 | - ⌨️ Add your custom CSS to the website 14 | - 🎨 Get the color palette of the website 15 | - 🧭 DOM Navigation 16 | - 🎯 Color picker (Chromium only) 17 | 18 | ## Installation 19 | - `yarn install` to install all dependencies 20 | - `yarn watch:dev` for development 21 | - `yarn build` build for production 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/docs/image.png -------------------------------------------------------------------------------- /manifest.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); 3 | 4 | /** 5 | * After changing, please reload the extension at `chrome://extensions` 6 | * @type {chrome.runtime.ManifestV3} 7 | */ 8 | const manifest = { 9 | manifest_version: 3, 10 | default_locale: 'en', 11 | /** 12 | * if you want to support multiple languages, you can use the following reference 13 | * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization 14 | */ 15 | name: '__MSG_extensionName__', 16 | version: packageJson.version, 17 | description: '__MSG_extensionDescription__', 18 | permissions: ['activeTab', 'scripting', 'storage'], 19 | action: {}, 20 | background: { 21 | service_worker: 'src/pages/background/index.js', 22 | type: 'module', 23 | }, 24 | icons: { 25 | 128: 'icon-128.png', 26 | }, 27 | web_accessible_resources: [ 28 | { 29 | resources: [ 30 | 'assets/js/*.js', 31 | 'assets/woff2/*.woff2', 32 | 'assets/css/*.css', 33 | 'icon-128.png', 34 | 'icon-34.png', 35 | ], 36 | matches: ['*://*/*'], 37 | }, 38 | ], 39 | }; 40 | 41 | if (process.env.__DEV__) { 42 | manifest.permissions.push('tabs'); 43 | // manifest.host_permissions = ['http://*/*']; 44 | } 45 | 46 | export default manifest; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspect-css", 3 | "version": "1.0.4", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "vue-tsc --noEmit && vite build", 7 | "build:firefox": "vue-tsc --noEmit && cross-env __FIREFOX__=true vite build", 8 | "build:watch": "cross-env __DEV__=true vite build -w --mode development", 9 | "build:firefox:watch": "cross-env __DEV__=true __FIREFOX__=true vite build -w --mode development", 10 | "build:hmr": "rollup --config utils/reload/rollup.config.mjs", 11 | "wss": "node utils/reload/initReloadServer.js", 12 | "dev": "pnpm build:hmr && (run-p wss build:watch)", 13 | "dev:firefox": "pnpm build:hmr && (run-p wss build:firefox:watch)", 14 | "test": "vitest", 15 | "commitlint": "commitlint --edit", 16 | "lint": "eslint src --ext .ts,.js,.tsx,.jsx", 17 | "lint:fix": "pnpm lint --fix", 18 | "prettier": "prettier . --write", 19 | "prepare": "husky install", 20 | "build:zip": "node ./utils/scripts/build-zip.js", 21 | "build:prod": "pnpm build:prod-chrome && pnpm build:prod-firefox", 22 | "build:prod-chrome": "pnpm build && pnpm build:zip", 23 | "build:prod-firefox": "pnpm build:firefox && cross-env __FIREFOX__=true pnpm build:zip" 24 | }, 25 | "type": "module", 26 | "dependencies": { 27 | "@codemirror/autocomplete": "^6.12.0", 28 | "@codemirror/commands": "^6.3.3", 29 | "@codemirror/lang-css": "^6.2.1", 30 | "@codemirror/lang-html": "^6.4.8", 31 | "@codemirror/language": "^6.10.0", 32 | "@codemirror/state": "^6.4.0", 33 | "@codemirror/view": "^6.23.0", 34 | "@floating-ui/vue": "^1.0.3", 35 | "@medv/finder": "^3.1.0", 36 | "@uiw/codemirror-extensions-color": "^4.21.21", 37 | "@uiw/codemirror-themes": "^4.21.21", 38 | "@vueuse/core": "^10.7.2", 39 | "@vueuse/motion": "^2.2.3", 40 | "class-variance-authority": "^0.7.0", 41 | "color2k": "^2.0.3", 42 | "colorthief": "^2.4.0", 43 | "css-tree": "^2.3.1", 44 | "file-saver": "^2.0.5", 45 | "jszip": "^3.10.1", 46 | "lucide-vue-next": "^0.321.0", 47 | "mitt": "^3.0.1", 48 | "radix-vue": "^1.3.2", 49 | "specificity": "^1.0.0", 50 | "type-fest": "^4.10.2", 51 | "vue": "^3.5.0", 52 | "webextension-polyfill": "0.10.0" 53 | }, 54 | "devDependencies": { 55 | "@commitlint/cli": "18.4.4", 56 | "@commitlint/config-conventional": "18.6.0", 57 | "@rollup/plugin-typescript": "11.1.6", 58 | "@rushstack/eslint-patch": "^1.6.1", 59 | "@types/chrome": "0.0.251", 60 | "@types/css-tree": "^2.3.5", 61 | "@types/node": "20.8.10", 62 | "@types/webextension-polyfill": "^0.10.7", 63 | "@types/ws": "8.5.8", 64 | "@typescript-eslint/eslint-plugin": "6.19.1", 65 | "@typescript-eslint/parser": "6.20.0", 66 | "@vitejs/plugin-vue": "^5.0.3", 67 | "@vue/eslint-config-airbnb": "^8.0.0", 68 | "archiver": "^6.0.1", 69 | "autoprefixer": "^10.4.16", 70 | "chokidar": "3.5.3", 71 | "cross-env": "7.0.3", 72 | "eslint": "8.56.0", 73 | "eslint-config-prettier": "9.0.0", 74 | "eslint-import-resolver-typescript": "^3.6.1", 75 | "eslint-plugin-import": "2.29.0", 76 | "eslint-plugin-prettier": "5.0.1", 77 | "eslint-plugin-vue": "^9.20.0", 78 | "fs-extra": "11.1.1", 79 | "husky": "8.0.3", 80 | "jsdom": "^24.0.0", 81 | "lint-staged": "15.2.0", 82 | "npm-run-all": "4.1.5", 83 | "postcss": "^8.4.33", 84 | "prettier": "3.1.0", 85 | "rollup": "4.3.0", 86 | "tailwindcss": "^3.4.1", 87 | "ts-loader": "9.5.0", 88 | "tslib": "2.6.2", 89 | "typescript": "^5.3.3", 90 | "vite": "5.4.3", 91 | "vue-eslint-parser": "^9.4.0", 92 | "vue-tsc": "^2.1.4", 93 | "ws": "8.14.2" 94 | }, 95 | "lint-staged": { 96 | "*.{js,jsx,ts,tsx}": [ 97 | "prettier --write", 98 | "eslint --fix" 99 | ] 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /public/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "description": "Extension description", 4 | "message": "The easiest way to inspect and edit CSS" 5 | }, 6 | "extensionName": { 7 | "description": "Extension name", 8 | "message": "Inspect CSS" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /public/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/public/icon-128.png -------------------------------------------------------------------------------- /public/icon-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/public/icon-34.png -------------------------------------------------------------------------------- /src/assets/fonts/ibm-plex-mono-v19-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/src/assets/fonts/ibm-plex-mono-v19-latin-regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/poppins-v20-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/src/assets/fonts/poppins-v20-latin-600.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/poppins-v20-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/src/assets/fonts/poppins-v20-latin-regular.woff2 -------------------------------------------------------------------------------- /src/assets/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/src/assets/img/background.png -------------------------------------------------------------------------------- /src/assets/style/fonts.css: -------------------------------------------------------------------------------- 1 | /* poppins-regular - latin */ 2 | @font-face { 3 | font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ 4 | font-family: 'Poppins'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: url('../fonts/poppins-v20-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ 8 | } 9 | /* poppins-600 - latin */ 10 | @font-face { 11 | font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ 12 | font-family: 'Poppins'; 13 | font-style: normal; 14 | font-weight: 600; 15 | src: url('../fonts/poppins-v20-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ 16 | } 17 | 18 | /* ibm-plex-mono-regular - latin */ 19 | @font-face { 20 | font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ 21 | font-family: 'IBM Plex Mono'; 22 | font-style: normal; 23 | font-weight: 400; 24 | src: url('../fonts/ibm-plex-mono-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/style/theme.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :host, 6 | :root { 7 | --background: 222.2 84% 4.9%; 8 | --foreground: 210 40% 98%; 9 | --primary: 217.22 91.22% 59.8%; 10 | --foreground: 210 40% 98%; 11 | --card: 222.2 84% 4.9%; 12 | --card-foreground: 210 40% 98%; 13 | --popover: 222.2 84% 4.9%; 14 | --popover-foreground: 210 40% 98%; 15 | --secondary: 217.2 32.6% 17.5%; 16 | --secondary-foreground: 210 40% 98%; 17 | --muted: 217.2 32.6% 17.5%; 18 | --muted-foreground: 215 20.2% 65.1%; 19 | --accent: 217.2 32.6% 17.5%; 20 | --accent-foreground: 210 40% 98%; 21 | --destructive: 0 62.8% 30.6%; 22 | --destructive-foreground: 210 40% 98%; 23 | --border: 217.2 32.6% 17.5%; 24 | --input: 217.2 32.6% 17.5%; 25 | --ring: 224.3 76.3% 48%; 26 | 27 | --radius: 12px; 28 | } 29 | 30 | @layer base { 31 | * { 32 | @apply border-border; 33 | } 34 | } 35 | 36 | .horizontal-center { 37 | position: absolute; 38 | left: 50%; 39 | transform: translateX(-50%); 40 | } 41 | 42 | .horizontal-center.top { 43 | top: 5px; 44 | } 45 | 46 | .horizontal-center.bottom { 47 | bottom: 5px; 48 | } 49 | 50 | .vertical-center { 51 | position: absolute; 52 | top: 50%; 53 | transform: translateY(-50%); 54 | } 55 | 56 | .vertical-center.left { 57 | left: 5px; 58 | } 59 | 60 | .vertical-center.right { 61 | right: 5px; 62 | } 63 | 64 | .kbd { 65 | @apply rounded-sm border border-b-2 px-1 py-0.5 inline-flex items-center justify-center bg-popover min-w-2 min-h-2; 66 | } 67 | 68 | .tooltip { 69 | @apply invisible absolute; 70 | } 71 | 72 | .has-tooltip:hover .tooltip { 73 | @apply visible z-40; 74 | } -------------------------------------------------------------------------------- /src/environment.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace NodeJS { 3 | interface ProcessEnv { 4 | __DEV__: string; 5 | __FIREFOX__: string; 6 | } 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'virtual:reload-on-update-in-background-script' { 2 | export const reloadOnUpdate: (watchPath: string) => void; 3 | export default reloadOnUpdate; 4 | } 5 | 6 | declare module 'virtual:reload-on-update-in-view' { 7 | const refreshOnUpdate: (watchPath: string) => void; 8 | export default refreshOnUpdate; 9 | } 10 | 11 | declare module '*.svg' { 12 | import React = require('react'); 13 | export const ReactComponent: React.SFC>; 14 | const src: string; 15 | export default src; 16 | } 17 | 18 | declare module '*.jpg' { 19 | const content: string; 20 | export default content; 21 | } 22 | 23 | declare module '*.png' { 24 | const content: string; 25 | export default content; 26 | } 27 | 28 | declare module '*.json' { 29 | const content: string; 30 | export default content; 31 | } 32 | 33 | interface ColorSelectionOptions { 34 | signal?: AbortSignal; 35 | } 36 | 37 | interface ColorSelectionResult { 38 | sRGBHex: string; 39 | } 40 | 41 | interface EyeDropper { 42 | open: (options?: ColorSelectionOptions) => Promise; 43 | } 44 | 45 | interface EyeDropperConstructor { 46 | new (): EyeDropper; 47 | } 48 | 49 | interface Window { 50 | EyeDropper?: EyeDropperConstructor | undefined; 51 | } 52 | 53 | declare const VITE_IS_FIREFOX: boolean; 54 | -------------------------------------------------------------------------------- /src/interfaces/app.interface.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kholid060/inspect-css/00271fbb6529508d880c81559dbad31e26279bad/src/interfaces/app.interface.ts -------------------------------------------------------------------------------- /src/lib.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'colorthief' { 2 | export type RGBColor = [number, number, number]; 3 | 4 | export default class ColorThief { 5 | getColor: (img: HTMLImageElement, quality?: number) => Promise; 6 | getPalette: ( 7 | img: HTMLImageElement, 8 | colorCount?: number, 9 | quality?: number, 10 | ) => Promise; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/codemirror/css.ts: -------------------------------------------------------------------------------- 1 | import { parser } from './lang-css'; 2 | import { 3 | LRLanguage, 4 | continuedIndent, 5 | indentNodeProp, 6 | foldNodeProp, 7 | foldInside, 8 | LanguageSupport, 9 | } from '@codemirror/language'; 10 | import { cssCompletionSource } from '@codemirror/lang-css'; 11 | 12 | /// A language provider based on the [Lezer CSS 13 | /// parser](https://github.com/lezer-parser/css), extended with 14 | /// highlighting and indentation information. 15 | export const cssLanguage = LRLanguage.define({ 16 | name: 'css', 17 | // @ts-expect-error IDK 18 | parser: parser.configure({ 19 | props: [ 20 | indentNodeProp.add({ 21 | Declaration: continuedIndent(), 22 | }), 23 | foldNodeProp.add({ 24 | 'Block KeyframeList': foldInside, 25 | }), 26 | ], 27 | }), 28 | languageData: { 29 | commentTokens: { block: { open: '/*', close: '*/' } }, 30 | indentOnInput: /^\s*\}$/, 31 | wordChars: '-', 32 | }, 33 | }); 34 | 35 | /// Language support for CSS. 36 | export function css() { 37 | return new LanguageSupport( 38 | cssLanguage, 39 | cssLanguage.data.of({ autocomplete: cssCompletionSource }), 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/codemirror/extensions.ts: -------------------------------------------------------------------------------- 1 | import { BlockInfo, EditorView, gutter, GutterMarker } from '@codemirror/view'; 2 | import { toggleBlockCommentByLine } from '@codemirror/commands'; 3 | 4 | function isCommented(view: EditorView, line: BlockInfo) { 5 | const lineText = view.state.sliceDoc(line.from, line.to).trim(); 6 | return lineText.startsWith('/*') && lineText.endsWith('*/'); 7 | } 8 | 9 | const gutterMarkerCheckbox = new (class extends GutterMarker { 10 | checked: boolean; 11 | 12 | constructor() { 13 | super(); 14 | this.checked = false; 15 | } 16 | 17 | toDOM() { 18 | const checkboxEl = document.createElement('input'); 19 | checkboxEl.setAttribute('type', 'checkbox'); 20 | checkboxEl.checked = this.checked; 21 | 22 | return checkboxEl; 23 | } 24 | })(); 25 | 26 | const gutterMarkerCheckboxChecked = new (class extends GutterMarker { 27 | toDOM() { 28 | const checkboxEl = document.createElement('input'); 29 | checkboxEl.setAttribute('type', 'checkbox'); 30 | checkboxEl.checked = true; 31 | 32 | return checkboxEl; 33 | } 34 | })(); 35 | 36 | export const toggleCommentGutter = [ 37 | gutter({ 38 | lineMarker(view, line) { 39 | if (line.from === line.to) return null; 40 | 41 | const insideComment = isCommented(view, line); 42 | return insideComment ? gutterMarkerCheckbox : gutterMarkerCheckboxChecked; 43 | }, 44 | initialSpacer: () => gutterMarkerCheckbox, 45 | domEventHandlers: { 46 | click(view, line) { 47 | view.dispatch({ selection: { anchor: line.to } }); 48 | toggleBlockCommentByLine(view); 49 | 50 | return true; 51 | }, 52 | }, 53 | }), 54 | ]; 55 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/highlight.js: -------------------------------------------------------------------------------- 1 | import { styleTags, tags as t } from '@lezer/highlight'; 2 | 3 | export const cssHighlighting = styleTags({ 4 | 'AtKeyword import charset namespace keyframes media supports': 5 | t.definitionKeyword, 6 | 'from to selector': t.keyword, 7 | NamespaceName: t.namespace, 8 | KeyframeName: t.labelName, 9 | KeyframeRangeName: t.operatorKeyword, 10 | TagName: t.tagName, 11 | ClassName: t.className, 12 | PseudoClassName: t.constant(t.className), 13 | IdName: t.labelName, 14 | 'FeatureName PropertyName': t.propertyName, 15 | AttributeName: t.attributeName, 16 | NumberLiteral: t.number, 17 | KeywordQuery: t.keyword, 18 | UnaryQueryOp: t.operatorKeyword, 19 | 'CallTag ValueName': t.atom, 20 | VariableName: t.variableName, 21 | Callee: t.operatorKeyword, 22 | Unit: t.unit, 23 | 'UniversalSelector NestingSelector': t.definitionOperator, 24 | MatchOp: t.compareOperator, 25 | 'ChildOp SiblingOp, LogicOp': t.logicOperator, 26 | BinOp: t.arithmeticOperator, 27 | Important: t.modifier, 28 | Comment: t.blockComment, 29 | ColorLiteral: t.color, 30 | 'ParenthesizedContent StringLiteral': t.string, 31 | ':': t.punctuation, 32 | 'PseudoOp #': t.derefOperator, 33 | '; ,': t.separator, 34 | '( )': t.paren, 35 | '[ ]': t.squareBracket, 36 | '{ }': t.brace, 37 | }); 38 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var lr = require('@lezer/lr'); 6 | var tokens_js = require('./tokens.js'); 7 | var highlight_js = require('./highlight.js'); 8 | 9 | // This file was generated by lezer-generator. You probably shouldn't edit it. 10 | const spec_callee = { 11 | __proto__: null, 12 | lang: 32, 13 | 'nth-child': 32, 14 | 'nth-last-child': 32, 15 | 'nth-of-type': 32, 16 | 'nth-last-of-type': 32, 17 | dir: 32, 18 | 'host-context': 32, 19 | url: 60, 20 | 'url-prefix': 60, 21 | domain: 60, 22 | regexp: 60, 23 | selector: 138, 24 | }; 25 | const spec_AtKeyword = { 26 | __proto__: null, 27 | '@import': 118, 28 | '@media': 142, 29 | '@charset': 146, 30 | '@namespace': 150, 31 | '@keyframes': 156, 32 | '@supports': 168, 33 | }; 34 | const spec_identifier = { __proto__: null, not: 132, only: 132 }; 35 | const parser = lr.LRParser.deserialize({ 36 | version: 14, 37 | states: 38 | ":^QYQ[OOOYQ[OOP!aOWOOO!fQXO'#CdOOQP'#Cc'#CcO#ZQ[O'#CfO#}QXO'#CaO$UQ[O'#ChO$aQ[O'#DTO$fQ[O'#DWOOQP'#Em'#EmO$kQWO'#DcO$pQWO'#EsQ${Q[O'#EsO&SQdO'#DgO&qQ[O'#DtO&SQdO'#DvO'SQ[O'#DxO'_Q[O'#D{O'gQ[O'#ERO'uQ[O'#ETOOQS'#El'#ElOOQS'#EW'#EWQOQWOOO'|Q[O'#EsP(TO#tO'#C_POOO)C@[)C@[OOQP'#Cg'#CgOOQP,59Q,59QO#ZQ[O,59QO(`Q[O'#E[O(zQWO,58{O)SQ[O,59SO$aQ[O,59oO$fQ[O,59rO(`Q[O,59uO(`Q[O,59wO(`Q[O,59xO*cQ[O'#DbOOQS,58{,58{OOQP'#Ck'#CkOOQO'#DR'#DROOQP,59S,59SO*jQWO,59SO*oQWO,59SOOQP'#DV'#DVOOQP,59o,59oOOQO'#DX'#DXO*tQ`O,59rO&SQdO,59}O*|Q[O'#E^O+ZQWO,5;_O+ZQWO,5;_OOQS-E8U-E8UOOQS'#Cp'#CpO&SQdO'#CqO+fQvO'#CsO,vQtO,5:ROOQO'#Cx'#CxO*oQWO'#CwO-[QWO'#CyO-aQ[O'#DOOOQS'#Ep'#EpOOQO'#Dj'#DjO-iQ[O'#DqO-wQWO'#EtO'gQ[O'#DoO.VQWO'#DrOOQO'#Eu'#EuO(}QWO,5:`O.[QpO,5:bOOQS'#Dz'#DzO.dQWO,5:dO.iQ[O,5:dOOQO'#D}'#D}O.qQWO,5:gO.vQWO,5:mO/OQWO,5:oPOOO'#EV'#EVP/WO#tO,58yPOOO,58y,58yOOQP1G.l1G.lOOQP'#Cd'#CdO/}QXO,5:vOOQO-E8Y-E8YOOQS1G.g1G.gOOQP1G.n1G.nO*jQWO1G.nO*oQWO1G.nOOQP1G/Z1G/ZO0[Q`O1G/^O0uQXO1G/aO1]QXO1G/cO1sQXO1G/dO2ZQWO,59|O2`Q[O'#DSO2gQdO'#CoOOQP1G/^1G/^O&SQdO1G/^O2nQtO1G/iOOQO,5:x,5:xO3UQ[O,5:xOOQO-E8[-E8[O3cQWO1G0yO3nQpO,59]OOQS,59_,59_O&SQdO,59aO3vQWO1G/mOOQS,59c,59cO3{Q!bO,59eOOQS'#DP'#DPOOQS'#EY'#EYO4TQ[O,59jOOQS,59j,59jO4]QWO'#DjO4hQWO,5:VO4mQWO,5:]O'gQ[O,5:XO'gQ[O'#E_O4uQWO,5;`O5QQWO,5:ZO(`Q[O,5:^OOQS1G/z1G/zOOQS1G/|1G/|OOQS1G0O1G0OO5cQWO1G0OO5hQdO'#EOOOQS1G0R1G0ROOQS1G0X1G0XOOQS1G0Z1G0ZPOOO-E8T-E8TPOOO1G.e1G.eOOQP7+$Y7+$YOOQP7+$x7+$xO&SQdO7+$xOOQS1G/h1G/hO5sQXO'#ErO5zQWO,59nO6PQtO'#EXO6wQdO'#EoO7RQWO,59ZO7WQpO7+$xO7`QtO'#E]O&SQdO'#E]O8aQdO7+%TOOQO7+%T7+%TOOQO1G0d1G0dOOQS1G.w1G.wOOQS1G.{1G.{OOQS7+%X7+%XO8tQWO1G/POOQS-E8W-E8WOOQS1G/U1G/UO&SQdO1G/qOOQO1G/w1G/wOOQO1G/s1G/sO8yQWO,5:yOOQO-E8]-E8]O9XQXO1G/xOOQS7+%j7+%jO9`QYO'#CsOOQO'#EQ'#EQO9kQ`O'#EPOOQO'#EP'#EPO9vQWO'#E`O:OQdO,5:jOOQS,5:j,5:jO:ZQpO<OAN>OO;{QdO,5:uOOQO-E8X-E8XOOQO<T![;'S%^;'S;=`%o<%lO%^l;TUo`Oy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^l;nYo`#e[Oy%^z!Q%^!Q![;g![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^l[[o`#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^n?VSt^Oy%^z;'S%^;'S;=`%o<%lO%^l?hWjWOy%^z!O%^!O!P;O!P!Q%^!Q![>T![;'S%^;'S;=`%o<%lO%^n@VU#bQOy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^~@nTjWOy%^z{@}{;'S%^;'S;=`%o<%lO%^~AUSo`#[~Oy%^z;'S%^;'S;=`%o<%lO%^lAg[#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^bBbU]QOy%^z![%^![!]Bt!];'S%^;'S;=`%o<%lO%^bB{S^Qo`Oy%^z;'S%^;'S;=`%o<%lO%^nC^S!Y^Oy%^z;'S%^;'S;=`%o<%lO%^dCoS|SOy%^z;'S%^;'S;=`%o<%lO%^bDQU!OQOy%^z!`%^!`!aDd!a;'S%^;'S;=`%o<%lO%^bDkS!OQo`Oy%^z;'S%^;'S;=`%o<%lO%^bDzWOy%^z!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^bEk[![Qo`Oy%^z}%^}!OEd!O!Q%^!Q![Ed![!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^nFfSq^Oy%^z;'S%^;'S;=`%o<%lO%^nFwSp^Oy%^z;'S%^;'S;=`%o<%lO%^bGWUOy%^z#b%^#b#cGj#c;'S%^;'S;=`%o<%lO%^bGoUo`Oy%^z#W%^#W#XHR#X;'S%^;'S;=`%o<%lO%^bHYS!bQo`Oy%^z;'S%^;'S;=`%o<%lO%^bHiUOy%^z#f%^#f#gHR#g;'S%^;'S;=`%o<%lO%^fIQS!TUOy%^z;'S%^;'S;=`%o<%lO%^nIcS!S^Oy%^z;'S%^;'S;=`%o<%lO%^fItU!RQOy%^z!_%^!_!`6y!`;'S%^;'S;=`%o<%lO%^`JZP;=`<%l$}", 55 | tokenizers: [ 56 | tokens_js.descendant, 57 | tokens_js.unitToken, 58 | tokens_js.identifiers, 59 | 1, 60 | 2, 61 | 3, 62 | 4, 63 | new lr.LocalTokenGroup('m~RRYZ[z{a~~g~aO#^~~dP!P!Qg~lO#_~~', 28, 105), 64 | ], 65 | topRules: { StyleSheet: [0, 4], Styles: [1, 86] }, 66 | specialized: [ 67 | { term: 100, get: (value) => spec_callee[value] || -1 }, 68 | { term: 58, get: (value) => spec_AtKeyword[value] || -1 }, 69 | { term: 101, get: (value) => spec_identifier[value] || -1 }, 70 | ], 71 | tokenPrec: 1249, 72 | }); 73 | 74 | exports.parser = parser; 75 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/index.d.cts: -------------------------------------------------------------------------------- 1 | import { LRParser } from '@lezer/lr'; 2 | 3 | export const parser: LRParser; 4 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/index.d.ts: -------------------------------------------------------------------------------- 1 | import { LRParser } from '@lezer/lr'; 2 | 3 | export const parser: LRParser; 4 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/index.js: -------------------------------------------------------------------------------- 1 | import { LRParser, LocalTokenGroup } from '@lezer/lr'; 2 | import { descendant, unitToken, identifiers } from './tokens.js'; 3 | import { cssHighlighting } from './highlight.js'; 4 | 5 | // This file was generated by lezer-generator. You probably shouldn't edit it. 6 | const spec_callee = { 7 | __proto__: null, 8 | lang: 32, 9 | 'nth-child': 32, 10 | 'nth-last-child': 32, 11 | 'nth-of-type': 32, 12 | 'nth-last-of-type': 32, 13 | dir: 32, 14 | 'host-context': 32, 15 | url: 60, 16 | 'url-prefix': 60, 17 | domain: 60, 18 | regexp: 60, 19 | selector: 138, 20 | }; 21 | const spec_AtKeyword = { 22 | __proto__: null, 23 | '@import': 118, 24 | '@media': 142, 25 | '@charset': 146, 26 | '@namespace': 150, 27 | '@keyframes': 156, 28 | '@supports': 168, 29 | }; 30 | const spec_identifier = { __proto__: null, not: 132, only: 132 }; 31 | const parser = LRParser.deserialize({ 32 | version: 14, 33 | states: 34 | ":^QYQ[OOOYQ[OOP!aOWOOO!fQXO'#CdOOQP'#Cc'#CcO#ZQ[O'#CfO#}QXO'#CaO$UQ[O'#ChO$aQ[O'#DTO$fQ[O'#DWOOQP'#Em'#EmO$kQWO'#DcO$pQWO'#EsQ${Q[O'#EsO&SQdO'#DgO&qQ[O'#DtO&SQdO'#DvO'SQ[O'#DxO'_Q[O'#D{O'gQ[O'#ERO'uQ[O'#ETOOQS'#El'#ElOOQS'#EW'#EWQOQWOOO'|Q[O'#EsP(TO#tO'#C_POOO)C@[)C@[OOQP'#Cg'#CgOOQP,59Q,59QO#ZQ[O,59QO(`Q[O'#E[O(zQWO,58{O)SQ[O,59SO$aQ[O,59oO$fQ[O,59rO(`Q[O,59uO(`Q[O,59wO(`Q[O,59xO*cQ[O'#DbOOQS,58{,58{OOQP'#Ck'#CkOOQO'#DR'#DROOQP,59S,59SO*jQWO,59SO*oQWO,59SOOQP'#DV'#DVOOQP,59o,59oOOQO'#DX'#DXO*tQ`O,59rO&SQdO,59}O*|Q[O'#E^O+ZQWO,5;_O+ZQWO,5;_OOQS-E8U-E8UOOQS'#Cp'#CpO&SQdO'#CqO+fQvO'#CsO,vQtO,5:ROOQO'#Cx'#CxO*oQWO'#CwO-[QWO'#CyO-aQ[O'#DOOOQS'#Ep'#EpOOQO'#Dj'#DjO-iQ[O'#DqO-wQWO'#EtO'gQ[O'#DoO.VQWO'#DrOOQO'#Eu'#EuO(}QWO,5:`O.[QpO,5:bOOQS'#Dz'#DzO.dQWO,5:dO.iQ[O,5:dOOQO'#D}'#D}O.qQWO,5:gO.vQWO,5:mO/OQWO,5:oPOOO'#EV'#EVP/WO#tO,58yPOOO,58y,58yOOQP1G.l1G.lOOQP'#Cd'#CdO/}QXO,5:vOOQO-E8Y-E8YOOQS1G.g1G.gOOQP1G.n1G.nO*jQWO1G.nO*oQWO1G.nOOQP1G/Z1G/ZO0[Q`O1G/^O0uQXO1G/aO1]QXO1G/cO1sQXO1G/dO2ZQWO,59|O2`Q[O'#DSO2gQdO'#CoOOQP1G/^1G/^O&SQdO1G/^O2nQtO1G/iOOQO,5:x,5:xO3UQ[O,5:xOOQO-E8[-E8[O3cQWO1G0yO3nQpO,59]OOQS,59_,59_O&SQdO,59aO3vQWO1G/mOOQS,59c,59cO3{Q!bO,59eOOQS'#DP'#DPOOQS'#EY'#EYO4TQ[O,59jOOQS,59j,59jO4]QWO'#DjO4hQWO,5:VO4mQWO,5:]O'gQ[O,5:XO'gQ[O'#E_O4uQWO,5;`O5QQWO,5:ZO(`Q[O,5:^OOQS1G/z1G/zOOQS1G/|1G/|OOQS1G0O1G0OO5cQWO1G0OO5hQdO'#EOOOQS1G0R1G0ROOQS1G0X1G0XOOQS1G0Z1G0ZPOOO-E8T-E8TPOOO1G.e1G.eOOQP7+$Y7+$YOOQP7+$x7+$xO&SQdO7+$xOOQS1G/h1G/hO5sQXO'#ErO5zQWO,59nO6PQtO'#EXO6wQdO'#EoO7RQWO,59ZO7WQpO7+$xO7`QtO'#E]O&SQdO'#E]O8aQdO7+%TOOQO7+%T7+%TOOQO1G0d1G0dOOQS1G.w1G.wOOQS1G.{1G.{OOQS7+%X7+%XO8tQWO1G/POOQS-E8W-E8WOOQS1G/U1G/UO&SQdO1G/qOOQO1G/w1G/wOOQO1G/s1G/sO8yQWO,5:yOOQO-E8]-E8]O9XQXO1G/xOOQS7+%j7+%jO9`QYO'#CsOOQO'#EQ'#EQO9kQ`O'#EPOOQO'#EP'#EPO9vQWO'#E`O:OQdO,5:jOOQS,5:j,5:jO:ZQpO<OAN>OO;{QdO,5:uOOQO-E8X-E8XOOQO<T![;'S%^;'S;=`%o<%lO%^l;TUo`Oy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^l;nYo`#e[Oy%^z!Q%^!Q![;g![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^l[[o`#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^n?VSt^Oy%^z;'S%^;'S;=`%o<%lO%^l?hWjWOy%^z!O%^!O!P;O!P!Q%^!Q![>T![;'S%^;'S;=`%o<%lO%^n@VU#bQOy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^~@nTjWOy%^z{@}{;'S%^;'S;=`%o<%lO%^~AUSo`#[~Oy%^z;'S%^;'S;=`%o<%lO%^lAg[#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^bBbU]QOy%^z![%^![!]Bt!];'S%^;'S;=`%o<%lO%^bB{S^Qo`Oy%^z;'S%^;'S;=`%o<%lO%^nC^S!Y^Oy%^z;'S%^;'S;=`%o<%lO%^dCoS|SOy%^z;'S%^;'S;=`%o<%lO%^bDQU!OQOy%^z!`%^!`!aDd!a;'S%^;'S;=`%o<%lO%^bDkS!OQo`Oy%^z;'S%^;'S;=`%o<%lO%^bDzWOy%^z!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^bEk[![Qo`Oy%^z}%^}!OEd!O!Q%^!Q![Ed![!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^nFfSq^Oy%^z;'S%^;'S;=`%o<%lO%^nFwSp^Oy%^z;'S%^;'S;=`%o<%lO%^bGWUOy%^z#b%^#b#cGj#c;'S%^;'S;=`%o<%lO%^bGoUo`Oy%^z#W%^#W#XHR#X;'S%^;'S;=`%o<%lO%^bHYS!bQo`Oy%^z;'S%^;'S;=`%o<%lO%^bHiUOy%^z#f%^#f#gHR#g;'S%^;'S;=`%o<%lO%^fIQS!TUOy%^z;'S%^;'S;=`%o<%lO%^nIcS!S^Oy%^z;'S%^;'S;=`%o<%lO%^fItU!RQOy%^z!_%^!_!`6y!`;'S%^;'S;=`%o<%lO%^`JZP;=`<%l$}", 51 | tokenizers: [ 52 | descendant, 53 | unitToken, 54 | identifiers, 55 | 1, 56 | 2, 57 | 3, 58 | 4, 59 | new LocalTokenGroup('m~RRYZ[z{a~~g~aO#^~~dP!P!Qg~lO#_~~', 28, 105), 60 | ], 61 | topRules: { StyleSheet: [0, 4], Styles: [1, 86] }, 62 | specialized: [ 63 | { term: 100, get: (value) => spec_callee[value] || -1 }, 64 | { term: 58, get: (value) => spec_AtKeyword[value] || -1 }, 65 | { term: 101, get: (value) => spec_identifier[value] || -1 }, 66 | ], 67 | tokenPrec: 1249, 68 | }); 69 | 70 | export { parser }; 71 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/parser.terms.js: -------------------------------------------------------------------------------- 1 | // This file was generated by lezer-generator. You probably shouldn't edit it. 2 | export const descendantOp = 99, 3 | Unit = 1, 4 | callee = 100, 5 | identifier = 101, 6 | VariableName = 2, 7 | Comment = 3, 8 | StyleSheet = 4, 9 | RuleSet = 5, 10 | UniversalSelector = 6, 11 | NestingSelector = 9, 12 | ColorLiteral = 22, 13 | NumberLiteral = 23, 14 | StringLiteral = 24, 15 | BinOp = 26, 16 | CallExpression = 27, 17 | CallLiteral = 29, 18 | ParenthesizedContent = 31, 19 | MatchOp = 44, 20 | ChildOp = 46, 21 | SiblingOp = 49, 22 | Block = 52, 23 | Declaration = 53, 24 | Important = 55, 25 | ImportStatement = 57, 26 | AtKeyword = 58, 27 | LogicOp = 64, 28 | MediaStatement = 70, 29 | CharsetStatement = 72, 30 | NamespaceStatement = 74, 31 | KeyframesStatement = 77, 32 | KeyframeList = 80, 33 | KeyframeSelector = 81, 34 | SupportsStatement = 83, 35 | AtRule = 85, 36 | Styles = 86; 37 | -------------------------------------------------------------------------------- /src/lib/codemirror/lang-css/tokens.js: -------------------------------------------------------------------------------- 1 | /* Hand-written tokenizers for CSS tokens that can't be 2 | expressed by Lezer's built-in tokenizer. */ 3 | 4 | import { ExternalTokenizer } from '@lezer/lr'; 5 | import { 6 | callee, 7 | identifier, 8 | VariableName, 9 | descendantOp, 10 | Unit, 11 | } from './parser.terms.js'; 12 | 13 | const space = [ 14 | 9, 10, 11, 12, 13, 32, 133, 160, 5760, 8192, 8193, 8194, 8195, 8196, 8197, 15 | 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288, 16 | ]; 17 | const colon = 58, 18 | parenL = 40, 19 | underscore = 95, 20 | bracketL = 91, 21 | dash = 45, 22 | period = 46, 23 | hash = 35, 24 | percent = 37, 25 | ampersand = 38, 26 | backslash = 92, 27 | newline = 10; 28 | 29 | function isAlpha(ch) { 30 | return (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122) || ch >= 161; 31 | } 32 | 33 | function isDigit(ch) { 34 | return ch >= 48 && ch <= 57; 35 | } 36 | 37 | export const identifiers = new ExternalTokenizer((input, stack) => { 38 | for (let inside = false, dashes = 0, i = 0; ; i++) { 39 | let { next } = input; 40 | if ( 41 | isAlpha(next) || 42 | next == dash || 43 | next == underscore || 44 | (inside && isDigit(next)) 45 | ) { 46 | if (!inside && (next != dash || i > 0)) inside = true; 47 | if (dashes === i && next == dash) dashes++; 48 | input.advance(); 49 | } else if (next == backslash && input.peek(1) != newline) { 50 | input.advance(); 51 | if (input.next > -1) input.advance(); 52 | inside = true; 53 | } else { 54 | if (inside) 55 | input.acceptToken( 56 | next == parenL 57 | ? callee 58 | : dashes == 2 && stack.canShift(VariableName) 59 | ? VariableName 60 | : identifier, 61 | ); 62 | break; 63 | } 64 | } 65 | }); 66 | 67 | export const descendant = new ExternalTokenizer((input) => { 68 | if (space.includes(input.peek(-1))) { 69 | let { next } = input; 70 | if ( 71 | isAlpha(next) || 72 | next == underscore || 73 | next == hash || 74 | next == period || 75 | next == bracketL || 76 | (next == colon && isAlpha(input.peek(1))) || 77 | next == dash || 78 | next == ampersand 79 | ) 80 | input.acceptToken(descendantOp); 81 | } 82 | }); 83 | 84 | export const unitToken = new ExternalTokenizer((input) => { 85 | if (!space.includes(input.peek(-1))) { 86 | let { next } = input; 87 | if (next == percent) { 88 | input.advance(); 89 | input.acceptToken(Unit); 90 | } 91 | if (isAlpha(next)) { 92 | do { 93 | input.advance(); 94 | } while (isAlpha(input.next)); 95 | input.acceptToken(Unit); 96 | } 97 | } 98 | }); 99 | -------------------------------------------------------------------------------- /src/lib/codemirror/theme.ts: -------------------------------------------------------------------------------- 1 | import { tags as t } from '@lezer/highlight'; 2 | import { createTheme } from '@uiw/codemirror-themes'; 3 | import type { CreateThemeOptions } from '@uiw/codemirror-themes'; 4 | 5 | export const defaultSettingsTheme: CreateThemeOptions['settings'] = { 6 | background: '#1e1e1e', 7 | foreground: '#9cdcfe', 8 | caret: '#c6c6c6', 9 | selection: '#6199ff2f', 10 | selectionMatch: '#72a1ff59', 11 | lineHighlight: '#ffffff0f', 12 | gutterBackground: '#1e1e1e', 13 | gutterForeground: '#838383', 14 | gutterActiveForeground: '#fff', 15 | }; 16 | 17 | // Colors from https://www.nordtheme.com/docs/colors-and-palettes 18 | export const themeInit = (options?: Partial) => { 19 | const { theme = 'dark', settings = {}, styles = [] } = options || {}; 20 | return createTheme({ 21 | theme: theme, 22 | settings: { 23 | ...defaultSettingsTheme, 24 | ...settings, 25 | }, 26 | styles: [ 27 | { 28 | tag: [ 29 | t.keyword, 30 | t.operatorKeyword, 31 | t.modifier, 32 | t.color, 33 | t.constant(t.name), 34 | t.standard(t.name), 35 | t.standard(t.tagName), 36 | t.special(t.brace), 37 | t.atom, 38 | t.bool, 39 | t.special(t.variableName), 40 | ], 41 | color: '#569cd6', 42 | }, 43 | { 44 | tag: [t.controlKeyword, t.moduleKeyword], 45 | color: '#c586c0', 46 | }, 47 | { 48 | tag: [ 49 | t.name, 50 | t.deleted, 51 | t.character, 52 | t.macroName, 53 | t.propertyName, 54 | t.variableName, 55 | t.labelName, 56 | t.definition(t.name), 57 | ], 58 | color: '#9cdcfe', 59 | }, 60 | { tag: t.heading, fontWeight: 'bold', color: '#9cdcfe' }, 61 | { 62 | tag: [ 63 | t.typeName, 64 | t.className, 65 | t.tagName, 66 | t.number, 67 | t.changed, 68 | t.annotation, 69 | t.self, 70 | t.namespace, 71 | ], 72 | color: '#4ec9b0', 73 | }, 74 | { 75 | tag: [t.function(t.variableName), t.function(t.propertyName)], 76 | color: '#dcdcaa', 77 | }, 78 | { tag: [t.number], color: '#b5cea8' }, 79 | { 80 | tag: [ 81 | t.operator, 82 | t.punctuation, 83 | t.separator, 84 | t.url, 85 | t.escape, 86 | t.regexp, 87 | ], 88 | color: '#d4d4d4', 89 | }, 90 | { 91 | tag: [t.regexp], 92 | color: '#d16969', 93 | }, 94 | { 95 | tag: [ 96 | t.special(t.string), 97 | t.processingInstruction, 98 | t.string, 99 | t.inserted, 100 | ], 101 | color: '#ce9178', 102 | }, 103 | { tag: [t.angleBracket], color: '#808080' }, 104 | { tag: t.strong, fontWeight: 'bold' }, 105 | { tag: t.emphasis, fontStyle: 'italic' }, 106 | { tag: t.strikethrough, textDecoration: 'line-through' }, 107 | { tag: [t.meta, t.comment], color: '#6a9955' }, 108 | { tag: t.link, color: '#6a9955', textDecoration: 'underline' }, 109 | { tag: t.invalid, color: '#ff0000' }, 110 | ...styles, 111 | ], 112 | }); 113 | }; 114 | 115 | export const theme = themeInit(); 116 | -------------------------------------------------------------------------------- /src/lib/mitt.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | import { ElementProperties } from '../utils/getElProperties'; 3 | 4 | export type EmitterEvents = { 5 | 'content:remove-selected': void; 6 | 'content:el-selected': { el: Element; properties: ElementProperties }; 7 | }; 8 | 9 | export const emitter = mitt(); 10 | 11 | export default mitt; 12 | -------------------------------------------------------------------------------- /src/pages/background/index.ts: -------------------------------------------------------------------------------- 1 | import RuntimeMessage from '@root/src/utils/RuntimeMessage'; 2 | import reloadOnUpdate from 'virtual:reload-on-update-in-background-script'; 3 | import Browser from 'webextension-polyfill'; 4 | 5 | reloadOnUpdate('pages/background'); 6 | 7 | async function injectContentScript(tabId: number) { 8 | try { 9 | const isInjected = await new Promise((resolve) => { 10 | Browser.tabs 11 | .sendMessage(tabId, { type: 'init' }) 12 | .then(() => resolve(true)) 13 | .catch(() => resolve(false)); 14 | }); 15 | if (isInjected) return; 16 | 17 | await Browser.scripting.executeScript({ 18 | target: { tabId }, 19 | files: ['/src/pages/content/index.js'], 20 | }); 21 | } catch (error) { 22 | console.error(error); 23 | } 24 | } 25 | 26 | Browser.action.onClicked.addListener((tab) => { 27 | if (!tab.id) return; 28 | 29 | injectContentScript(tab.id); 30 | }); 31 | 32 | // DEV ONLY 33 | if (import.meta.env.MODE === 'development') { 34 | Browser.tabs.onUpdated.addListener((tabId, changeInfo) => { 35 | if (!changeInfo.status || changeInfo.status !== 'complete') return; 36 | 37 | Browser.tabs.get(tabId).then((tab) => { 38 | if (!tab.url || !tab.url.includes('localhost')) return; 39 | 40 | injectContentScript(tabId); 41 | }); 42 | }); 43 | } 44 | 45 | RuntimeMessage.onMessage('background:screenshot-tab', (sender) => { 46 | if (!sender.tab?.windowId) return Promise.resolve(''); 47 | 48 | return Browser.tabs.captureVisibleTab(sender.tab.windowId, { quality: 70 }); 49 | }); 50 | 51 | /** 52 | * Extension reloading is necessary because the browser automatically caches the css. 53 | * If you do not use the css of the content script, please delete it. 54 | */ 55 | 56 | console.log('background loaded'); 57 | -------------------------------------------------------------------------------- /src/pages/components/custom-css/CustomCSSElements.vue: -------------------------------------------------------------------------------- 1 | 62 | 137 | -------------------------------------------------------------------------------- /src/pages/components/custom-css/CustomCSSGlobal.vue: -------------------------------------------------------------------------------- 1 | 18 | 54 | -------------------------------------------------------------------------------- /src/pages/components/dom-navigation/DomNavigationItem.vue: -------------------------------------------------------------------------------- 1 | 49 | 114 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 50 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiCodemirror.vue: -------------------------------------------------------------------------------- 1 | 4 | 84 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiElementProperties.vue: -------------------------------------------------------------------------------- 1 | 27 | 45 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiElementSelector.vue: -------------------------------------------------------------------------------- 1 | 14 | 25 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiElementSpacing.vue: -------------------------------------------------------------------------------- 1 | 23 | 36 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiPopover.vue: -------------------------------------------------------------------------------- 1 | 16 | 30 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiSwitch.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 59 | -------------------------------------------------------------------------------- /src/pages/components/ui/UiTooltip.vue: -------------------------------------------------------------------------------- 1 | 16 | 41 | -------------------------------------------------------------------------------- /src/pages/content/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT USE import someModule from '...'; 3 | * 4 | * @issue-url https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/160 5 | * 6 | * Chrome extensions don't support modules in content scripts. 7 | * If you want to use other modules in content scripts, you need to import them via these files. 8 | * 9 | */ 10 | import('@pages/content/ui'); 11 | -------------------------------------------------------------------------------- /src/pages/content/ui/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 37 | -------------------------------------------------------------------------------- /src/pages/content/ui/app-plugin.ts: -------------------------------------------------------------------------------- 1 | import settingsStorage from '@root/src/storages/settings.storage'; 2 | import { ElementAppliedStyleRules } from '@root/src/utils/CSSRulesUtils'; 3 | import { EL_IDS } from '@root/src/utils/constant'; 4 | import { resetAppliedStyleValue } from '@root/src/utils/generate-element-css'; 5 | import { ElementBasicSelector } from '@root/src/utils/getElProperties'; 6 | import { Plugin, Ref, ref, inject, shallowReactive } from 'vue'; 7 | 8 | export interface StyleData { 9 | index: number; 10 | items: Record; 11 | dirtyItems: Ref>; 12 | } 13 | export interface StyleDataItem { 14 | id: number; 15 | elSelector: string; 16 | basicSelector: ElementBasicSelector; 17 | currentProps: ElementAppliedStyleRules; 18 | initialProps: ElementAppliedStyleRules; 19 | } 20 | 21 | export interface AppState { 22 | paused: boolean; 23 | showGrid: boolean; 24 | tempHide: boolean; 25 | interactive: boolean; 26 | hasGlobalCSS: boolean; 27 | } 28 | export interface AppStateProvider { 29 | state: AppState; 30 | destroy: () => void; 31 | styleData: StyleData; 32 | shadowRoot: ShadowRoot; 33 | addDirtyStyleItem(id: number): void; 34 | removeDirtyStyleItem(id: number): void; 35 | updateState: (state: Partial) => void; 36 | addStyleItem( 37 | detail: Omit, 38 | ): StyleDataItem; 39 | updateStyleItem(id: number, detail: Partial): void; 40 | } 41 | 42 | const APP_PROVIDER_KEY = Symbol('app-provider'); 43 | 44 | const styleData: StyleData = { 45 | index: 0, 46 | items: {}, 47 | dirtyItems: ref({}), 48 | }; 49 | 50 | export function useAppProvider() { 51 | const state = inject(APP_PROVIDER_KEY)!; 52 | 53 | return state; 54 | } 55 | 56 | // To-do: use pinia? 57 | 58 | const defaultState: AppState = { 59 | paused: false, 60 | tempHide: false, 61 | showGrid: false, 62 | interactive: true, 63 | hasGlobalCSS: false, 64 | }; 65 | 66 | export const appPlugin: Plugin = { 67 | install(app, shadowRoot: ShadowRoot) { 68 | const appState = shallowReactive({ ...defaultState }); 69 | 70 | settingsStorage.get().then((settings) => { 71 | Object.assign(appState, settings); 72 | }); 73 | 74 | function updateState(newState: Partial) { 75 | Object.assign(appState, newState); 76 | 77 | if ( 78 | Object.hasOwn(newState, 'showGrid') || 79 | Object.hasOwn(newState, 'interactive') 80 | ) { 81 | settingsStorage.set({ 82 | showGrid: appState.showGrid, 83 | interactive: appState.interactive, 84 | }); 85 | } 86 | } 87 | function addStyleItem(detail: Omit) { 88 | const data: StyleDataItem = { 89 | ...detail, 90 | id: styleData.index, 91 | currentProps: detail.initialProps, 92 | initialProps: resetAppliedStyleValue(detail.initialProps), 93 | }; 94 | styleData.items[styleData.index] = data; 95 | styleData.index += 1; 96 | 97 | return data; 98 | } 99 | function updateStyleItem(id: number, detail: Partial) { 100 | if (!Object.hasOwn(styleData.items, id)) return; 101 | 102 | styleData.items[id] = { 103 | ...styleData.items[id], 104 | ...detail, 105 | }; 106 | } 107 | function addDirtyStyleItem(id: number) { 108 | styleData.dirtyItems.value[id] = true; 109 | } 110 | function removeDirtyStyleItem(id: number) { 111 | delete styleData.dirtyItems.value[id]; 112 | } 113 | 114 | function destroy() { 115 | Object.assign(appState, defaultState); 116 | 117 | app.unmount(); 118 | shadowRoot.host.remove(); 119 | 120 | document.getElementById(EL_IDS.customCSS)?.remove(); 121 | } 122 | 123 | app.provide(APP_PROVIDER_KEY, { 124 | destroy, 125 | styleData, 126 | shadowRoot, 127 | updateState, 128 | addStyleItem, 129 | state: appState, 130 | updateStyleItem, 131 | addDirtyStyleItem, 132 | removeDirtyStyleItem, 133 | }); 134 | }, 135 | }; 136 | -------------------------------------------------------------------------------- /src/pages/content/ui/app/AppElementDetail.vue: -------------------------------------------------------------------------------- 1 | 65 | 242 | -------------------------------------------------------------------------------- /src/pages/content/ui/app/AppElementScanner.vue: -------------------------------------------------------------------------------- 1 | 40 | 194 | -------------------------------------------------------------------------------- /src/pages/content/ui/app/AppToolbar.vue: -------------------------------------------------------------------------------- 1 | 114 | 164 | -------------------------------------------------------------------------------- /src/pages/content/ui/app/detail/DetailAttributes.vue: -------------------------------------------------------------------------------- 1 |