feat(elements): element crumbs
This commit is contained in:
@@ -83,16 +83,16 @@ Check dom element status.
|
||||
|overrideEventTarget|boolean|Catch Event Listeners|
|
||||
|observeElement |boolean|Auto Refresh |
|
||||
|
||||
### set
|
||||
### select
|
||||
|
||||
Set dom element to show.
|
||||
Select node to show.
|
||||
|
||||
|Name|Type |Desc |
|
||||
|----|-------|------------------|
|
||||
|el |element|Element to display|
|
||||
|Name|Type |Desc |
|
||||
|----|---------|-------------------|
|
||||
|node|ChildNode|Node to be selected|
|
||||
|
||||
```javascript
|
||||
elements.set(document.body);
|
||||
elements.select(document.body);
|
||||
```
|
||||
|
||||
## Network
|
||||
|
||||
@@ -12,39 +12,32 @@ import isNaN from 'licia/isNaN'
|
||||
import isNum from 'licia/isNum'
|
||||
import each from 'licia/each'
|
||||
import keys from 'licia/keys'
|
||||
import isNull from 'licia/isNull'
|
||||
import trim from 'licia/trim'
|
||||
import CssStore from './CssStore'
|
||||
import LunaModal from 'luna-modal'
|
||||
import { formatNodeName } from './util'
|
||||
import { pxToNum, classPrefix as c } from '../lib/util'
|
||||
|
||||
export default class Detail {
|
||||
constructor($container) {
|
||||
this._$container = $container
|
||||
this._curEl = document.documentElement
|
||||
this._bindEvent()
|
||||
}
|
||||
show(el) {
|
||||
const data = this._getData(el)
|
||||
this._curEl = el
|
||||
this._rmDefComputedStyle = true
|
||||
this._computedStyleSearchKeyword = ''
|
||||
this._render()
|
||||
}
|
||||
_toggleAllComputedStyle() {
|
||||
this._rmDefComputedStyle = !this._rmDefComputedStyle
|
||||
|
||||
let parents = ''
|
||||
if (!isEmpty(data.parents)) {
|
||||
parents = `<ul class="${c('parents')}">
|
||||
${map(data.parents, ({ text, idx }) => {
|
||||
return `<li>
|
||||
<div class="${c('parent')}" data-idx="${idx}">${text}</div>
|
||||
<span class="${c('icon-arrow-right')}"></span>
|
||||
</li>`
|
||||
}).join('')}
|
||||
</ul>`
|
||||
}
|
||||
|
||||
let children = ''
|
||||
if (data.children) {
|
||||
children = `<ul class="${c('children')}">
|
||||
${map(data.children, ({ isCmt, idx, isEl, text }) => {
|
||||
return `<li class="${c(
|
||||
`child ${isCmt ? 'green' : ''} ${isEl ? 'active-effect' : ''}`
|
||||
)}" data-idx="${idx}">${text}</li>`
|
||||
}).join('')}
|
||||
</ul>`
|
||||
}
|
||||
this._render()
|
||||
}
|
||||
_render() {
|
||||
const data = this._getData(this._curEl)
|
||||
|
||||
let attribute = '<tr><td>Empty</td></tr>'
|
||||
if (!isEmpty(data.attributes)) {
|
||||
@@ -177,11 +170,9 @@ export default class Detail {
|
||||
</div>`
|
||||
}
|
||||
|
||||
const html = `${parents}
|
||||
<div class="${c('breadcrumb')}">
|
||||
const html = `<div class="${c('breadcrumb')}">
|
||||
${data.name}
|
||||
</div>
|
||||
${children}
|
||||
${attribute}
|
||||
${styles}
|
||||
${computedStyle}
|
||||
@@ -197,10 +188,8 @@ export default class Detail {
|
||||
const { className, id, attributes, tagName } = el
|
||||
|
||||
ret.computedStyleSearchKeyword = this._computedStyleSearchKeyword
|
||||
ret.parents = getParents(el)
|
||||
ret.children = formatChildNodes(el.childNodes)
|
||||
ret.attributes = formatAttr(attributes)
|
||||
ret.name = formatElName({ tagName, id, className, attributes })
|
||||
ret.name = formatNodeName({ tagName, id, className, attributes })
|
||||
|
||||
const events = el.erudaEvents
|
||||
if (events && keys(events).length !== 0) ret.listeners = events
|
||||
@@ -260,7 +249,20 @@ export default class Detail {
|
||||
|
||||
return ret
|
||||
}
|
||||
_bindEvent() {}
|
||||
_bindEvent() {
|
||||
this._$container
|
||||
.on('click', c('.toggle-all-computed-style'), () =>
|
||||
this._toggleAllComputedStyle()
|
||||
)
|
||||
.on('click', c('.computed-style-search'), () => {
|
||||
LunaModal.prompt('Filter').then((filter) => {
|
||||
if (isNull(filter)) return
|
||||
filter = trim(filter)
|
||||
this._computedStyleSearchKeyword = filter
|
||||
this._render()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function processStyleRules(style) {
|
||||
@@ -281,42 +283,6 @@ const formatAttr = (attributes) =>
|
||||
return { name, value }
|
||||
})
|
||||
|
||||
function formatChildNodes(nodes) {
|
||||
const ret = []
|
||||
|
||||
for (let i = 0, len = nodes.length; i < len; i++) {
|
||||
const child = nodes[i]
|
||||
const nodeType = child.nodeType
|
||||
|
||||
if (nodeType === 3 || nodeType === 8) {
|
||||
const val = child.nodeValue.trim()
|
||||
if (val !== '')
|
||||
ret.push({
|
||||
text: val,
|
||||
isCmt: nodeType === 8,
|
||||
idx: i,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const isSvg = !isStr(child.className)
|
||||
|
||||
if (
|
||||
nodeType === 1 &&
|
||||
child.id !== 'eruda' &&
|
||||
(isSvg || child.className.indexOf('eruda') < 0)
|
||||
) {
|
||||
ret.push({
|
||||
text: formatElName(child),
|
||||
isEl: true,
|
||||
idx: i,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
const regColor = /rgba?\((.*?)\)/g
|
||||
const regCssUrl = /url\("?(.*?)"?\)/g
|
||||
|
||||
@@ -332,50 +298,6 @@ function processStyleRule(val) {
|
||||
.replace(regCssUrl, (match, url) => `url("${wrapLink(url)}")`)
|
||||
}
|
||||
|
||||
function formatElName(data, { noAttr = false } = {}) {
|
||||
const { id, className, attributes } = data
|
||||
|
||||
let ret = `<span class="eruda-tag-name-color">${data.tagName.toLowerCase()}</span>`
|
||||
|
||||
if (id !== '') ret += `<span class="eruda-function-color">#${id}</span>`
|
||||
|
||||
if (isStr(className)) {
|
||||
let classes = ''
|
||||
each(className.split(/\s+/g), (val) => {
|
||||
if (val.trim() === '') return
|
||||
classes += `.${val}`
|
||||
})
|
||||
ret += `<span class="eruda-attribute-name-color">${classes}</span>`
|
||||
}
|
||||
|
||||
if (!noAttr) {
|
||||
each(attributes, (attr) => {
|
||||
const name = attr.name
|
||||
if (name === 'id' || name === 'class' || name === 'style') return
|
||||
ret += ` <span class="eruda-attribute-name-color">${name}</span><span class="eruda-operator-color">="</span><span class="eruda-string-color">${attr.value}</span><span class="eruda-operator-color">"</span>`
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function getParents(el) {
|
||||
const ret = []
|
||||
let i = 0
|
||||
let parent = el.parentNode
|
||||
|
||||
while (parent && parent.nodeType === 1) {
|
||||
ret.push({
|
||||
text: formatElName(parent, { noAttr: true }),
|
||||
idx: i++,
|
||||
})
|
||||
|
||||
parent = parent.parentNode
|
||||
}
|
||||
|
||||
return ret.reverse()
|
||||
}
|
||||
|
||||
function getInlineStyle(style) {
|
||||
const ret = {
|
||||
selectorText: 'element.style',
|
||||
|
||||
@@ -12,13 +12,14 @@ import isBool from 'licia/isBool'
|
||||
import safeGet from 'licia/safeGet'
|
||||
import nextTick from 'licia/nextTick'
|
||||
import Emitter from 'licia/Emitter'
|
||||
import isNull from 'licia/isNull'
|
||||
import trim from 'licia/trim'
|
||||
import LunaModal from 'luna-modal'
|
||||
import map from 'licia/map'
|
||||
import isEmpty from 'licia/isEmpty'
|
||||
import toNum from 'licia/toNum'
|
||||
import LunaDomViewer from 'luna-dom-viewer'
|
||||
import { isErudaEl, classPrefix as c } from '../lib/util'
|
||||
import evalCss from '../lib/evalCss'
|
||||
import Detail from './Detail'
|
||||
import { formatNodeName } from './util'
|
||||
|
||||
export default class Elements extends Tool {
|
||||
constructor() {
|
||||
@@ -27,11 +28,9 @@ export default class Elements extends Tool {
|
||||
this._style = evalCss(require('./Elements.scss'))
|
||||
|
||||
this.name = 'elements'
|
||||
this._rmDefComputedStyle = true
|
||||
this._highlightElement = false
|
||||
this._selectElement = false
|
||||
this._observeElement = true
|
||||
this._computedStyleSearchKeyword = ''
|
||||
this._history = []
|
||||
|
||||
Emitter.mixin(this)
|
||||
@@ -62,22 +61,19 @@ export default class Elements extends Tool {
|
||||
|
||||
if (this._observeElement) this._enableObserver()
|
||||
if (!this._curEl) this._setEl(this._htmlEl)
|
||||
this._render()
|
||||
}
|
||||
hide() {
|
||||
this._disableObserver()
|
||||
|
||||
return super.hide()
|
||||
}
|
||||
set(e) {
|
||||
if (e === this._curEl) return
|
||||
|
||||
this._setEl(e)
|
||||
this._render()
|
||||
this._updateHistory()
|
||||
|
||||
this.emit('change', e)
|
||||
|
||||
// To be removed in 3.0.0
|
||||
set(node) {
|
||||
return this.select(node)
|
||||
}
|
||||
select(node) {
|
||||
this._setEl(node)
|
||||
this.emit('change', node)
|
||||
return this
|
||||
}
|
||||
overrideEventTarget() {
|
||||
@@ -117,18 +113,30 @@ export default class Elements extends Tool {
|
||||
|
||||
$el.html(
|
||||
c(`<div class="control">
|
||||
<span class="icon icon-select select"></span>
|
||||
<span class="icon icon-eye show"></span>
|
||||
</div>
|
||||
<div class="dom-viewer-container">
|
||||
<div class="dom-viewer"></div>
|
||||
</div>
|
||||
<div class="detail"></div>`)
|
||||
<span class="icon icon-select select"></span>
|
||||
<span class="icon icon-eye show"></span>
|
||||
</div>
|
||||
<div class="dom-viewer-container">
|
||||
<div class="dom-viewer"></div>
|
||||
</div>
|
||||
<div class="crumbs"></div>
|
||||
<div class="detail"></div>`)
|
||||
)
|
||||
|
||||
this._$detail = $el.find(c('.detail'))
|
||||
this._$domViewer = $el.find(c('.dom-viewer'))
|
||||
this._$control = $el.find(c('.control'))
|
||||
this._$crumbs = $el.find(c('.crumbs'))
|
||||
}
|
||||
_renderCrumbs() {
|
||||
const crumbs = getCrumbs(this._curEl)
|
||||
let html = ''
|
||||
if (!isEmpty(crumbs)) {
|
||||
html = map(crumbs, ({ text, idx }) => {
|
||||
return `<li class="${c('crumb')}" data-idx="${idx}">${text}</div></li>`
|
||||
}).join('')
|
||||
}
|
||||
this._$crumbs.html(html)
|
||||
}
|
||||
_back() {
|
||||
if (this._curEl === this._htmlEl) return
|
||||
@@ -146,38 +154,6 @@ export default class Elements extends Tool {
|
||||
const select = this._select
|
||||
|
||||
this._$el
|
||||
.on('click', '.eruda-child', function () {
|
||||
const idx = $(this).data('idx')
|
||||
const curEl = self._curEl
|
||||
const el = curEl.childNodes[idx]
|
||||
|
||||
if (el && el.nodeType === 3) {
|
||||
const curTagName = curEl.tagName
|
||||
let type
|
||||
|
||||
switch (curTagName) {
|
||||
case 'SCRIPT':
|
||||
type = 'js'
|
||||
break
|
||||
case 'STYLE':
|
||||
type = 'css'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const sources = container.get('sources')
|
||||
|
||||
if (sources) {
|
||||
sources.set(type, el.nodeValue)
|
||||
container.showTool('sources')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
!isElExist(el) ? self._render() : self.set(el)
|
||||
})
|
||||
.on('click', '.eruda-listener-content', function () {
|
||||
const text = $(this).text()
|
||||
const sources = container.get('sources')
|
||||
@@ -195,25 +171,17 @@ export default class Elements extends Tool {
|
||||
container.showTool('sources')
|
||||
}
|
||||
})
|
||||
.on('click', '.eruda-parent', function () {
|
||||
let idx = $(this).data('idx')
|
||||
const curEl = self._curEl
|
||||
let el = curEl.parentNode
|
||||
.on('click', c('.crumb'), function () {
|
||||
let idx = toNum($(this).data('idx'))
|
||||
let el = self._curEl
|
||||
|
||||
while (idx-- && el.parentNode) el = el.parentNode
|
||||
while (idx-- && el.parentElement) {
|
||||
el = el.parentElement
|
||||
}
|
||||
|
||||
!isElExist(el) ? self._render() : self.set(el)
|
||||
})
|
||||
.on('click', '.eruda-toggle-all-computed-style', () =>
|
||||
this._toggleAllComputedStyle()
|
||||
)
|
||||
.on('click', '.eruda-computed-style-search', () => {
|
||||
LunaModal.prompt('Filter').then((filter) => {
|
||||
if (isNull(filter)) return
|
||||
filter = trim(filter)
|
||||
this._computedStyleSearchKeyword = filter
|
||||
this._render()
|
||||
})
|
||||
if (isElExist(el)) {
|
||||
self.set(el)
|
||||
}
|
||||
})
|
||||
|
||||
this._$control
|
||||
@@ -221,11 +189,8 @@ export default class Elements extends Tool {
|
||||
.on('click', c('.show'), () => this._detail.show(this._curEl))
|
||||
|
||||
select.on('select', (target) => this.set(target))
|
||||
}
|
||||
_toggleAllComputedStyle() {
|
||||
this._rmDefComputedStyle = !this._rmDefComputedStyle
|
||||
|
||||
this._render()
|
||||
this._domViewer.on('select', this._setEl)
|
||||
}
|
||||
_enableObserver() {
|
||||
this._observer.observe(this._htmlEl, {
|
||||
@@ -260,11 +225,14 @@ export default class Elements extends Tool {
|
||||
select.disable()
|
||||
}
|
||||
}
|
||||
_setEl(el) {
|
||||
_setEl = (el) => {
|
||||
if (el === this._curEl) return
|
||||
|
||||
this._curEl = el
|
||||
this._domViewer.select(el)
|
||||
this._renderCrumbs()
|
||||
|
||||
this._highlight.setEl(el)
|
||||
this._rmDefComputedStyle = true
|
||||
|
||||
const parentQueue = []
|
||||
|
||||
@@ -274,16 +242,8 @@ export default class Elements extends Tool {
|
||||
parent = parent.parentNode
|
||||
}
|
||||
this._curParentQueue = parentQueue
|
||||
}
|
||||
_render() {
|
||||
if (!isElExist(this._curEl)) return this._back()
|
||||
|
||||
this._highlight[this._highlightElement ? 'show' : 'hide']()
|
||||
}
|
||||
_renderHtml(html) {
|
||||
if (html === this._lastHtml) return
|
||||
this._lastHtml = html
|
||||
this._$showArea.html(html)
|
||||
this._updateHistory()
|
||||
}
|
||||
_updateHistory() {
|
||||
const console = this._container.get('console')
|
||||
@@ -409,3 +369,19 @@ const getWinEventProto = () => {
|
||||
}
|
||||
|
||||
const isElExist = (val) => isEl(val) && val.parentNode
|
||||
|
||||
function getCrumbs(el) {
|
||||
const ret = []
|
||||
let i = 0
|
||||
|
||||
while (el) {
|
||||
ret.push({
|
||||
text: formatNodeName(el, { noAttr: true }),
|
||||
idx: i++,
|
||||
})
|
||||
|
||||
el = el.parentElement
|
||||
}
|
||||
|
||||
return ret.reverse()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#elements {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 24px;
|
||||
font-size: 14px;
|
||||
.control {
|
||||
@include control();
|
||||
@@ -16,32 +17,24 @@
|
||||
height: 100%;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.detail {
|
||||
@include absolute();
|
||||
@include overflow-auto(y);
|
||||
z-index: 10;
|
||||
display: none;
|
||||
background: var(--background);
|
||||
}
|
||||
.parents {
|
||||
@include overflow-auto(x);
|
||||
overflow-y: hidden;
|
||||
.crumbs {
|
||||
@include absolute(100%, 24px);
|
||||
top: initial;
|
||||
line-height: 24px;
|
||||
bottom: 0;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--darker-background);
|
||||
color: var(--primary);
|
||||
padding: 0 $padding;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: $font-size-s;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
font-size: $font-size;
|
||||
overflow: hidden;
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 0 7px;
|
||||
display: inline-block;
|
||||
.parent {
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover,
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
background: var(--highlight);
|
||||
}
|
||||
}
|
||||
.icon-arrow-right {
|
||||
@@ -50,6 +43,13 @@
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
.detail {
|
||||
@include absolute();
|
||||
@include overflow-auto(y);
|
||||
z-index: 10;
|
||||
display: none;
|
||||
background: var(--background);
|
||||
}
|
||||
.breadcrumb {
|
||||
@include breadcrumb();
|
||||
cursor: pointer;
|
||||
@@ -83,33 +83,6 @@
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.children {
|
||||
background: var(--darker-background);
|
||||
color: var(--foreground);
|
||||
margin-bottom: 10px !important;
|
||||
border-bottom: 1px solid var(--border);
|
||||
li {
|
||||
@include overflow-auto(x);
|
||||
cursor: default;
|
||||
padding: $padding;
|
||||
border-top: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
transition: background $anim-duration, color $anim-duration;
|
||||
span {
|
||||
transition: color $anim-duration;
|
||||
}
|
||||
&.active-effect {
|
||||
cursor: pointer;
|
||||
}
|
||||
&.active-effect:active {
|
||||
background: var(--highlight);
|
||||
color: var(--select-foreground);
|
||||
span {
|
||||
color: var(--select-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.attributes {
|
||||
font-size: $font-size-s;
|
||||
a {
|
||||
|
||||
36
src/Elements/util.js
Normal file
36
src/Elements/util.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import each from 'licia/each'
|
||||
import isStr from 'licia/isStr'
|
||||
import { classPrefix as c } from '../lib/util'
|
||||
|
||||
export function formatNodeName(node, { noAttr = false } = {}) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return `<span class="${c('tag-name-color')}">(text)</span>`
|
||||
} else if (node.nodeType === Node.COMMENT_NODE) {
|
||||
return `<span class="${c('tag-name-color')}"><!--></span>`
|
||||
}
|
||||
|
||||
const { id, className, attributes } = node
|
||||
|
||||
let ret = `<span class="eruda-tag-name-color">${node.tagName.toLowerCase()}</span>`
|
||||
|
||||
if (id !== '') ret += `<span class="eruda-function-color">#${id}</span>`
|
||||
|
||||
if (isStr(className)) {
|
||||
let classes = ''
|
||||
each(className.split(/\s+/g), (val) => {
|
||||
if (val.trim() === '') return
|
||||
classes += `.${val}`
|
||||
})
|
||||
ret += `<span class="eruda-attribute-name-color">${classes}</span>`
|
||||
}
|
||||
|
||||
if (!noAttr) {
|
||||
each(attributes, (attr) => {
|
||||
const name = attr.name
|
||||
if (name === 'id' || name === 'class' || name === 'style') return
|
||||
ret += ` <span class="eruda-attribute-name-color">${name}</span><span class="eruda-operator-color">="</span><span class="eruda-string-color">${attr.value}</span><span class="eruda-operator-color">"</span>`
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
Reference in New Issue
Block a user