├── .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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------