├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.html ├── package.json ├── prettier.config.js ├── src ├── htmlComment.hbs ├── htmlTag.hbs ├── index.js ├── style.scss ├── template.hbs └── textNode.hbs └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | commonjs: true 6 | }, 7 | extends: 'standard', 8 | rules: { 9 | quotes: ['error', 'single'], 10 | 'space-before-function-paren': 'off' 11 | }, 12 | parserOptions: { 13 | sourceType: 'module' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | eruda-dom.js 3 | eruda-dom.js.map 4 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .eslintrc.js 3 | .gitignore 4 | .npmignore 5 | .prettierignore 6 | .travis.yml 7 | index.html 8 | prettier.config.js 9 | webpack.config.js -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | eruda-dom.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | script: 5 | - npm run ci -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.0.0 (3 Jan 2020) 2 | 3 | * feat: theme support 4 | 5 | ## v1.1.0 (11 Oct 2019) 6 | 7 | * feat: in sync with elements panel [#1](https://github.com/liriliri/eruda-dom/issues/1) 8 | 9 | ## v1.0.2 (27 Mar 2019) 10 | 11 | * Hide empty attribute value 12 | * Add underline to links -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present liriliri 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eruda-dom 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build status][travis-image]][travis-url] 5 | [![License][license-image]][npm-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/eruda-dom.svg 8 | [npm-url]: https://npmjs.org/package/eruda-dom 9 | [travis-image]: https://img.shields.io/travis/liriliri/eruda-dom.svg 10 | [travis-url]: https://travis-ci.org/liriliri/eruda-dom 11 | [license-image]: https://img.shields.io/npm/l/eruda-dom.svg 12 | 13 | Eruda plugin for navigating dom tree. 14 | 15 | ## Demo 16 | 17 | Browse it on your phone: 18 | [http://eruda.liriliri.io/?plugin=dom](http://eruda.liriliri.io/?plugin=dom) 19 | 20 | ## Install 21 | 22 | ```bash 23 | npm install eruda-dom --save 24 | ``` 25 | 26 | ```javascript 27 | eruda.add(erudaDom); 28 | ``` 29 | 30 | Make sure Eruda is loaded before this plugin, otherwise won't work. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Eruda-dom 7 | 8 | 9 | 10 |
Hello world!
11 |
testwordbreaktestwordbreaktestwordbreaktestwordbreaktestwordbreaktestwordbreak
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eruda-dom", 3 | "version": "2.0.0", 4 | "main": "eruda-dom.js", 5 | "description": "Eruda plugin for navigating dom tree", 6 | "scripts": { 7 | "dev": "webpack-dev-server --host 0.0.0.0", 8 | "build": "webpack -p", 9 | "format": "prettier src/index.js src/style.scss *.js --write", 10 | "ci": "npm run lint && npm run build", 11 | "lint": "eslint src/**/*.js", 12 | "lint:fix": "npm run lint -- --fix" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/liriliri/eruda-dom.git" 17 | }, 18 | "keywords": [ 19 | "eruda", 20 | "plugin", 21 | "dom" 22 | ], 23 | "author": "redhoodsu", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/liriliri/eruda-dom/issues" 27 | }, 28 | "homepage": "https://github.com/liriliri/eruda-dom#readme", 29 | "devDependencies": { 30 | "autoprefixer": "^7.2.2", 31 | "babel-core": "^6.26.3", 32 | "babel-loader": "^7.1.5", 33 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 34 | "babel-plugin-transform-runtime": "^6.23.0", 35 | "babel-preset-env": "^1.7.0", 36 | "css-loader": "^0.28.7", 37 | "eruda": "^2.0.0", 38 | "eslint": "^5.4.0", 39 | "eslint-config-standard": "^12.0.0", 40 | "eslint-plugin-import": "^2.14.0", 41 | "eslint-plugin-node": "^7.0.1", 42 | "eslint-plugin-promise": "^4.0.0", 43 | "eslint-plugin-standard": "^3.1.0", 44 | "handlebars": "^4.0.11", 45 | "handlebars-loader": "^1.6.0", 46 | "node-sass": "^4.7.2", 47 | "postcss": "^6.0.14", 48 | "postcss-class-prefix": "^0.3.0", 49 | "postcss-loader": "^2.0.9", 50 | "prettier": "^1.14.2", 51 | "sass-loader": "^6.0.6", 52 | "webpack": "^3.10.0", 53 | "webpack-dev-server": "^2.9.7" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | tabWidth: 2, 4 | semi: false 5 | } 6 | -------------------------------------------------------------------------------- /src/htmlComment.hbs: -------------------------------------------------------------------------------- 1 | <!-- {{value}} --> -------------------------------------------------------------------------------- /src/htmlTag.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{! 3 | }}<{{! 4 | }}{{tagName}}{{! 5 | }}{{#each attributes}} 6 | 7 | {{name}}{{#if value}}="{{value}}"{{/if}}{{! 8 | }}{{! 9 | }}{{/each}}{{! 10 | }}>{{! 11 | }}{{! 12 | }}{{#if hasTail}}{{! 13 | }}{{#if text}}{{text}}{{else}}…{{/if}}{{! 14 | }}{{! 15 | }}<{{! 16 | }}/{{tagName}}{{! 17 | }}>{{! 18 | }} 19 | {{/if}} 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(eruda) { 2 | let { evalCss, each, $, toArr } = eruda.util 3 | 4 | class Dom extends eruda.Tool { 5 | constructor() { 6 | super() 7 | this.name = 'dom' 8 | this._style = evalCss(require('./style.scss')) 9 | this._isInit = false 10 | this._htmlTagTpl = require('./htmlTag.hbs') 11 | this._textNodeTpl = require('./textNode.hbs') 12 | this._selectedEl = document.documentElement 13 | this._htmlCommentTpl = require('./htmlComment.hbs') 14 | this._elementChangeHandler = el => { 15 | if (this._selectedEl === el) return 16 | this.select(el) 17 | } 18 | } 19 | init($el, container) { 20 | super.init($el) 21 | this._container = container 22 | $el.html(require('./template.hbs')()) 23 | this._$domTree = $el.find('.eruda-dom-tree') 24 | 25 | this._bindEvent() 26 | } 27 | show() { 28 | super.show() 29 | 30 | if (!this._isInit) this._initTree() 31 | } 32 | hide() { 33 | super.hide() 34 | } 35 | select(el) { 36 | const els = [] 37 | els.push(el) 38 | while (el.parentElement) { 39 | els.unshift(el.parentElement) 40 | el = el.parentElement 41 | } 42 | while (els.length > 0) { 43 | el = els.shift() 44 | const erudaDom = el.erudaDom 45 | if (erudaDom) { 46 | if (erudaDom.close && erudaDom.open) { 47 | erudaDom.close() 48 | erudaDom.open() 49 | } 50 | } else { 51 | break 52 | } 53 | if (els.length === 0 && el.erudaDom) { 54 | el.erudaDom.select() 55 | } 56 | } 57 | } 58 | destroy() { 59 | super.destroy() 60 | evalCss.remove(this._style) 61 | const elements = this._container.get('elements') 62 | if (elements) { 63 | elements.off('change', this._elementChangeHandler) 64 | } 65 | } 66 | _bindEvent() { 67 | const container = this._container 68 | 69 | const elements = container.get('elements') 70 | if (elements) { 71 | elements.on('change', this._elementChangeHandler) 72 | } 73 | 74 | this._$el.on('click', '.eruda-inspect', () => { 75 | this._setElement(this._selectedEl) 76 | if (elements) container.showTool('elements') 77 | }) 78 | } 79 | _setElement(el) { 80 | const elements = this._container.get('elements') 81 | if (!elements) return 82 | 83 | elements.set(el) 84 | } 85 | _initTree() { 86 | this._isInit = true 87 | 88 | this._renderChildren(null, this._$domTree) 89 | this.select(document.body) 90 | } 91 | _renderChildren(node, $container) { 92 | let children 93 | if (!node) { 94 | children = [document.documentElement] 95 | } else { 96 | children = toArr(node.childNodes) 97 | } 98 | 99 | const container = $container.get(0) 100 | 101 | if (node) { 102 | children.push({ 103 | nodeType: 'END_TAG', 104 | node 105 | }) 106 | } 107 | each(children, child => this._renderChild(child, container)) 108 | } 109 | _renderChild(child, container) { 110 | const $tag = createEl('li') 111 | let isEndTag = false 112 | 113 | $tag.addClass('eruda-tree-item') 114 | if (child.nodeType === child.ELEMENT_NODE) { 115 | const childCount = child.childNodes.length 116 | const expandable = childCount > 0 117 | const data = { 118 | ...getHtmlTagData(child), 119 | hasTail: expandable 120 | } 121 | const hasOneTextNode = 122 | childCount === 1 && child.childNodes[0].nodeType === child.TEXT_NODE 123 | if (hasOneTextNode) { 124 | data.text = child.childNodes[0].nodeValue 125 | } 126 | $tag.html(this._htmlTagTpl(data)) 127 | if (expandable && !hasOneTextNode) { 128 | $tag.addClass('eruda-expandable') 129 | } 130 | } else if (child.nodeType === child.TEXT_NODE) { 131 | const value = child.nodeValue 132 | if (value.trim() === '') return 133 | 134 | $tag.html( 135 | this._textNodeTpl({ 136 | value 137 | }) 138 | ) 139 | } else if (child.nodeType === child.COMMENT_NODE) { 140 | const value = child.nodeValue 141 | if (value.trim() === '') return 142 | 143 | $tag.html( 144 | this._htmlCommentTpl({ 145 | value 146 | }) 147 | ) 148 | } else if (child.nodeType === 'END_TAG') { 149 | isEndTag = true 150 | child = child.node 151 | $tag.html( 152 | `</${child.tagName.toLocaleLowerCase()}>` 153 | ) 154 | } else { 155 | return 156 | } 157 | const $children = createEl('ul') 158 | $children.addClass('eruda-children') 159 | 160 | container.appendChild($tag.get(0)) 161 | container.appendChild($children.get(0)) 162 | 163 | if (child.nodeType !== child.ELEMENT_NODE) return 164 | 165 | let erudaDom = {} 166 | 167 | if ($tag.hasClass('eruda-expandable')) { 168 | const open = () => { 169 | $tag.html( 170 | this._htmlTagTpl({ 171 | ...getHtmlTagData(child), 172 | hasTail: false 173 | }) 174 | ) 175 | $tag.addClass('eruda-expanded') 176 | this._renderChildren(child, $children) 177 | } 178 | const close = () => { 179 | $children.html('') 180 | $tag.html( 181 | this._htmlTagTpl({ 182 | ...getHtmlTagData(child), 183 | hasTail: true 184 | }) 185 | ) 186 | $tag.rmClass('eruda-expanded') 187 | } 188 | const toggle = () => { 189 | if ($tag.hasClass('eruda-expanded')) { 190 | close() 191 | } else { 192 | open() 193 | } 194 | } 195 | $tag.on('click', '.eruda-toggle-btn', e => { 196 | e.stopPropagation() 197 | toggle() 198 | }) 199 | erudaDom = { 200 | open, 201 | close 202 | } 203 | } 204 | 205 | const select = () => { 206 | this._$el.find('.eruda-selected').rmClass('eruda-selected') 207 | $tag.addClass('eruda-selected') 208 | this._selectedEl = child 209 | this._setElement(child) 210 | } 211 | $tag.on('click', select) 212 | erudaDom.select = select 213 | if (!isEndTag) child.erudaDom = erudaDom 214 | } 215 | } 216 | 217 | function getHtmlTagData(el) { 218 | const ret = {} 219 | 220 | ret.tagName = el.tagName.toLocaleLowerCase() 221 | const attributes = [] 222 | each(el.attributes, attribute => { 223 | const { name, value } = attribute 224 | attributes.push({ 225 | name, 226 | value, 227 | underline: isUrlAttribute(el, name) 228 | }) 229 | }) 230 | ret.attributes = attributes 231 | 232 | return ret 233 | } 234 | 235 | function isUrlAttribute(el, name) { 236 | const tagName = el.tagName 237 | if ( 238 | tagName === 'SCRIPT' || 239 | tagName === 'IMAGE' || 240 | tagName === 'VIDEO' || 241 | tagName === 'AUDIO' 242 | ) { 243 | if (name === 'src') return true 244 | } 245 | 246 | if (tagName === 'LINK') { 247 | if (name === 'href') return true 248 | } 249 | 250 | return false 251 | } 252 | 253 | function createEl(name) { 254 | return $(document.createElement(name)) 255 | } 256 | 257 | return new Dom() 258 | } 259 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | @mixin overflow-auto($direction: 'both') { 2 | @if $direction == 'both' { 3 | overflow: auto; 4 | } @else { 5 | overflow-#{$direction}: auto; 6 | } 7 | -webkit-overflow-scrolling: touch; 8 | } 9 | 10 | .dom { 11 | padding-bottom: 40px; 12 | .dom-tree { 13 | @include overflow-auto(y); 14 | overflow-x: hidden; 15 | word-wrap: break-word; 16 | padding: 10px 10px 10px 25px; 17 | font-size: 12px; 18 | height: 100%; 19 | font-family: Consolas, Lucida Console, Monaco, MonoSpace; 20 | cursor: default; 21 | & * { 22 | user-select: text; 23 | } 24 | .tree-item { 25 | line-height: 16px; 26 | min-height: 16px; 27 | position: relative; 28 | z-index: 10; 29 | .toggle-btn { 30 | position: absolute; 31 | display: block; 32 | width: 12px; 33 | height: 12px; 34 | left: -12px; 35 | top: 2px; 36 | } 37 | .selection { 38 | position: absolute; 39 | display: none; 40 | left: -10000px; 41 | right: -10000px; 42 | top: 0; 43 | bottom: 0; 44 | z-index: -1; 45 | background: var(--contrast); 46 | } 47 | &.selected { 48 | &.expandable.expanded:before { 49 | border-left-color: transparent; 50 | } 51 | .selection { 52 | display: block; 53 | } 54 | } 55 | .html-tag { 56 | color: var(--tag-name-color); 57 | .tag-name { 58 | color: var(--tag-name-color); 59 | } 60 | .attribute-name { 61 | color: var(--attribute-name-color); 62 | } 63 | .attribute-value { 64 | color: var(--string-color); 65 | &.attribute-underline { 66 | text-decoration: underline; 67 | } 68 | } 69 | } 70 | .html-comment { 71 | color: var(--comment-color); 72 | } 73 | &.expandable:before { 74 | content: ''; 75 | width: 0; 76 | height: 0; 77 | border: 4px solid transparent; 78 | position: absolute; 79 | border-left-color: var(--foreground); 80 | left: -10px; 81 | top: 4px; 82 | } 83 | &.expandable.expanded:before { 84 | border-top-color: var(--foreground); 85 | border-left-color: transparent; 86 | left: -12px; 87 | top: 6px; 88 | } 89 | } 90 | .children { 91 | padding-left: 15px; 92 | } 93 | } 94 | .inspect { 95 | position: absolute; 96 | left: 0; 97 | bottom: 0; 98 | color: var(--foreground); 99 | border-top: 1px solid var(--border); 100 | width: 100%; 101 | background: var(--darker-background); 102 | display: block; 103 | height: 40px; 104 | line-height: 40px; 105 | text-decoration: none; 106 | text-align: center; 107 | margin-top: 10px; 108 | transition: background 0.3s; 109 | &:active { 110 | color: var(--select-foreground); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/template.hbs: -------------------------------------------------------------------------------- 1 | 2 |
Inspect Selected Element
-------------------------------------------------------------------------------- /src/textNode.hbs: -------------------------------------------------------------------------------- 1 | "{{value}}" -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer') 2 | const postcss = require('postcss') 3 | const webpack = require('webpack') 4 | const pkg = require('./package.json') 5 | const classPrefix = require('postcss-class-prefix') 6 | 7 | const banner = pkg.name + ' v' + pkg.version + ' ' + pkg.homepage 8 | 9 | var exports = { 10 | devtool: 'source-map', 11 | entry: './src/index.js', 12 | devServer: { 13 | contentBase: './', 14 | port: 3000 15 | }, 16 | output: { 17 | path: __dirname, 18 | filename: 'eruda-dom.js', 19 | publicPath: '/assets/', 20 | library: ['erudaDom'], 21 | libraryTarget: 'umd' 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['env'], 32 | plugins: ['transform-runtime', 'transform-object-rest-spread'] 33 | } 34 | } 35 | }, 36 | { 37 | test: /\.scss$/, 38 | loaders: [ 39 | 'css-loader', 40 | { 41 | loader: 'postcss-loader', 42 | options: { 43 | plugins: function() { 44 | return [ 45 | postcss.plugin('postcss-namespace', function() { 46 | // Add '.dev-tools .tools ' to every selector. 47 | return function(root) { 48 | root.walkRules(function(rule) { 49 | if (!rule.selectors) return rule 50 | 51 | rule.selectors = rule.selectors.map(function(selector) { 52 | return '.dev-tools .tools ' + selector 53 | }) 54 | }) 55 | } 56 | }), 57 | classPrefix('eruda-'), 58 | autoprefixer 59 | ] 60 | } 61 | } 62 | }, 63 | 'sass-loader' 64 | ] 65 | }, 66 | { 67 | test: /\.hbs$/, 68 | loader: 'handlebars-loader' 69 | } 70 | ] 71 | }, 72 | plugins: [new webpack.BannerPlugin(banner)] 73 | } 74 | 75 | module.exports = exports 76 | --------------------------------------------------------------------------------