feat(elements): integrate dom viewer

This commit is contained in:
redhoodsu
2022-12-17 08:25:35 +08:00
parent 0897a0363d
commit 0494088499
9 changed files with 503 additions and 505 deletions

View File

@@ -65,6 +65,7 @@ module.exports = {
path.resolve(__dirname, '../node_modules/luna-tab'),
path.resolve(__dirname, '../node_modules/luna-data-grid'),
path.resolve(__dirname, '../node_modules/luna-object-viewer'),
path.resolve(__dirname, '../node_modules/luna-dom-viewer'),
],
use: [
{

View File

@@ -66,7 +66,7 @@
"licia": "^1.37.0",
"luna-console": "^1.2.0",
"luna-data-grid": "^0.3.1",
"luna-dom-viewer": "^1.0.2",
"luna-dom-viewer": "^1.1.1",
"luna-modal": "^1.0.0",
"luna-notification": "^0.1.4",
"luna-object-viewer": "^0.2.4",

430
src/Elements/Detail.js Normal file
View File

@@ -0,0 +1,430 @@
import isEmpty from 'licia/isEmpty'
import lowerCase from 'licia/lowerCase'
import pick from 'licia/pick'
import toStr from 'licia/toStr'
import map from 'licia/map'
import escape from 'licia/escape'
import startWith from 'licia/startWith'
import contain from 'licia/contain'
import unique from 'licia/unique'
import isStr from 'licia/isStr'
import isNaN from 'licia/isNaN'
import isNum from 'licia/isNum'
import each from 'licia/each'
import keys from 'licia/keys'
import CssStore from './CssStore'
import { pxToNum, classPrefix as c } from '../lib/util'
export default class Detail {
constructor($container) {
this._$container = $container
this._bindEvent()
}
show(el) {
const data = this._getData(el)
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>`
}
let attribute = '<tr><td>Empty</td></tr>'
if (!isEmpty(data.attributes)) {
attribute = map(data.attributes, ({ name, value }) => {
return `<tr>
<td class="${c('attribute-name-color')}">${escape(name)}</td>
<td class="${c('string-color')}">${value}</td>
</tr>`
}).join('')
}
attribute = `<div class="${c('attributes section')}">
<h2>Attributes</h2>
<div class="${c('table-wrapper')}">
<table>
<tbody>
${attribute}
</tbody>
</table>
</div>
</div>`
let styles = ''
if (!isEmpty(data.styles)) {
const style = map(data.styles, ({ selectorText, style }) => {
style = map(style, (val, key) => {
return `<div class="${c('rule')}"><span>${escape(
key
)}</span>: ${val};</div>`
}).join('')
return `<div class="${c('style-rules')}">
<div>${escape(selectorText)} {</div>
${style}
<div>}</div>
</div>`
}).join('')
styles = `<div class="${c('styles section')}">
<h2>Styles</h2>
<div class="${c('style-wrapper')}">
${style}
</div>
</div>`
}
let computedStyle = ''
if (data.computedStyle) {
let toggleButton = c(`<div class="btn toggle-all-computed-style">
<span class="icon-expand"></span>
</div>`)
if (data.rmDefComputedStyle) {
toggleButton = c(`<div class="btn toggle-all-computed-style">
<span class="icon-compress"></span>
</div>`)
}
const boxModel = data.boxModel
// prettier-ignore
const boxModelHtml = [`<div class="${c('box-model')}">`,
boxModel.position ? `<div class="${c('position')}">` : '',
boxModel.position ? `<div class="${c('label')}">position</div><div class="${c('top')}">${boxModel.position.top}</div><br><div class="${c('left')}">${boxModel.position.left}</div>` : '',
`<div class="${c('margin')}">`,
`<div class="${c('label')}">margin</div><div class="${c('top')}">${boxModel.margin.top}</div><br><div class="${c('left')}">${boxModel.margin.left}</div>`,
`<div class="${c('border')}">`,
`<div class="${c('label')}">border</div><div class="${c('top')}">${boxModel.border.top}</div><br><div class="${c('left')}">${boxModel.border.left}</div>`,
`<div class="${c('padding')}">`,
`<div class="${c('label')}">padding</div><div class="${c('top')}">${boxModel.padding.top}</div><br><div class="${c('left')}">${boxModel.padding.left}</div>`,
`<div class="${c('content')}">`,
`<span>${boxModel.content.width}</span>&nbsp;×&nbsp;<span>${boxModel.content.height}</span>`,
'</div>',
`<div class="${c('right')}">${boxModel.padding.right}</div><br><div class="${c('bottom')}">${boxModel.padding.bottom}</div>`,
'</div>',
`<div class="${c('right')}">${boxModel.border.right}</div><br><div class="${c('bottom')}">${boxModel.border.bottom}</div>`,
'</div>',
`<div class="${c('right')}">${boxModel.margin.right}</div><br><div class="${c('bottom')}">${boxModel.margin.bottom}</div>`,
'</div>',
boxModel.position ? `<div class="${c('right')}">${boxModel.position.right}</div><br><div class="${c('bottom')}">${boxModel.position.bottom}</div>` : '',
boxModel.position ? '</div>' : '',
'</div>'].join('')
computedStyle = `<div class="${c('computed-style section')}">
<h2>
Computed Style
${toggleButton}
<div class="${c('btn computed-style-search')}">
<span class="${c('icon-filter')}"></span>
</div>
${
data.computedStyleSearchKeyword
? `<div class="${c('btn search-keyword')}">${escape(
data.computedStyleSearchKeyword
)}</div>`
: ''
}
</h2>
${boxModelHtml}
<div class="${c('table-wrapper')}">
<table>
<tbody>
${map(data.computedStyle, (val, key) => {
return `<tr>
<td class="${c('key')}">${escape(key)}</td>
<td>${val}</td>
</tr>`
}).join('')}
</tbody>
</table>
</div>
</div>`
}
let listeners = ''
if (data.listeners) {
listeners = map(data.listeners, (listeners, key) => {
listeners = map(listeners, ({ useCapture, listenerStr }) => {
return `<li ${useCapture ? `class="${c('capture')}"` : ''}>${escape(
listenerStr
)}</li>`
}).join('')
return `<div class="${c('listener')}">
<div class="${c('listener-type')}">${escape(key)}</div>
<ul class="${c('listener-content')}">
${listeners}
</ul>
</div>`
}).join('')
listeners = `<div class="${c('listeners section')}">
<h2>Event Listeners</h2>
<div class="${c('listener-wrapper')}">
${listeners}
</div>
</div>`
}
const html = `${parents}
<div class="${c('breadcrumb')}">
${data.name}
</div>
${children}
${attribute}
${styles}
${computedStyle}
${listeners}`
this._$container.html(html).show()
}
_getData(el) {
const ret = {}
const cssStore = new CssStore(el)
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 })
const events = el.erudaEvents
if (events && keys(events).length !== 0) ret.listeners = events
if (needNoStyle(tagName)) return ret
let computedStyle = cssStore.getComputedStyle()
function getBoxModelValue(type) {
let keys = ['top', 'left', 'right', 'bottom']
if (type !== 'position') keys = map(keys, (key) => `${type}-${key}`)
if (type === 'border') keys = map(keys, (key) => `${key}-width`)
return {
top: boxModelValue(computedStyle[keys[0]], type),
left: boxModelValue(computedStyle[keys[1]], type),
right: boxModelValue(computedStyle[keys[2]], type),
bottom: boxModelValue(computedStyle[keys[3]], type),
}
}
const boxModel = {
margin: getBoxModelValue('margin'),
border: getBoxModelValue('border'),
padding: getBoxModelValue('padding'),
content: {
width: boxModelValue(computedStyle['width']),
height: boxModelValue(computedStyle['height']),
},
}
if (computedStyle['position'] !== 'static') {
boxModel.position = getBoxModelValue('position')
}
ret.boxModel = boxModel
const styles = cssStore.getMatchedCSSRules()
styles.unshift(getInlineStyle(el.style))
styles.forEach((style) => processStyleRules(style.style))
ret.styles = styles
if (this._rmDefComputedStyle) {
computedStyle = rmDefComputedStyle(computedStyle, styles)
}
ret.rmDefComputedStyle = this._rmDefComputedStyle
const computedStyleSearchKeyword = lowerCase(ret.computedStyleSearchKeyword)
if (computedStyleSearchKeyword) {
computedStyle = pick(computedStyle, (val, property) => {
return (
contain(property, computedStyleSearchKeyword) ||
contain(val, computedStyleSearchKeyword)
)
})
}
processStyleRules(computedStyle)
ret.computedStyle = computedStyle
return ret
}
_bindEvent() {}
}
function processStyleRules(style) {
each(style, (val, key) => (style[key] = processStyleRule(val)))
}
const formatAttr = (attributes) =>
map(attributes, (attr) => {
let { value } = attr
const { name } = attr
value = escape(value)
const isLink =
(name === 'src' || name === 'href') && !startWith(value, 'data')
if (isLink) value = wrapLink(value)
if (name === 'style') value = processStyleRule(value)
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
function processStyleRule(val) {
// For css custom properties, val is unable to retrieved.
val = toStr(val)
return val
.replace(
regColor,
'<span class="eruda-style-color" style="background-color: $&"></span>$&'
)
.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',
style: {},
}
for (let i = 0, len = style.length; i < len; i++) {
const s = style[i]
ret.style[s] = style[s]
}
return ret
}
function rmDefComputedStyle(computedStyle, styles) {
const ret = {}
let keepStyles = ['display', 'width', 'height']
each(styles, (style) => {
keepStyles = keepStyles.concat(keys(style.style))
})
keepStyles = unique(keepStyles)
each(computedStyle, (val, key) => {
if (!contain(keepStyles, key)) return
ret[key] = val
})
return ret
}
const NO_STYLE_TAG = ['script', 'style', 'meta', 'title', 'link', 'head']
const needNoStyle = (tagName) =>
NO_STYLE_TAG.indexOf(tagName.toLowerCase()) > -1
const wrapLink = (link) => `<a href="${link}" target="_blank">${link}</a>`
function boxModelValue(val, type) {
if (isNum(val)) return val
if (!isStr(val)) return ''
const ret = pxToNum(val)
if (isNaN(ret)) return val
if (type === 'position') return ret
return ret === 0 ? '' : ret
}

View File

@@ -1,35 +1,24 @@
import Tool from '../DevTools/Tool'
import CssStore from './CssStore'
import Highlight from './Highlight'
import Select from './Select'
import Settings from '../Settings/Settings'
import $ from 'licia/$'
import isEmpty from 'licia/isEmpty'
import keys from 'licia/keys'
import MutationObserver from 'licia/MutationObserver'
import each from 'licia/each'
import toStr from 'licia/toStr'
import isEl from 'licia/isEl'
import isStr from 'licia/isStr'
import map from 'licia/map'
import escape from 'licia/escape'
import startWith from 'licia/startWith'
import isFn from 'licia/isFn'
import isBool from 'licia/isBool'
import safeGet from 'licia/safeGet'
import isNaN from 'licia/isNaN'
import isNum from 'licia/isNum'
import nextTick from 'licia/nextTick'
import Emitter from 'licia/Emitter'
import contain from 'licia/contain'
import unique from 'licia/unique'
import isNull from 'licia/isNull'
import trim from 'licia/trim'
import lowerCase from 'licia/lowerCase'
import pick from 'licia/pick'
import LunaModal from 'luna-modal'
import { pxToNum, isErudaEl, classPrefix as c } from '../lib/util'
import LunaDomViewer from 'luna-dom-viewer'
import { isErudaEl, classPrefix as c } from '../lib/util'
import evalCss from '../lib/evalCss'
import Detail from './Detail'
export default class Elements extends Tool {
constructor() {
@@ -52,26 +41,14 @@ export default class Elements extends Tool {
this._container = container
$el.html('<div class="eruda-show-area"></div>')
this._$showArea = $el.find('.eruda-show-area')
$el.append(
c(`<div class="bottom-bar">
<div class="btn select">
<span class="icon icon-select"></span>
</div>
<div class="btn refresh">
<span class="icon icon-refresh"></span>
</div>
<div class="btn highlight">
<span class="icon icon-eye"></span>
</div>
<div class="btn reset">
<span class="icon icon-reset"></span>
</div>
</div>`)
)
this._initTpl()
this._htmlEl = document.documentElement
this._detail = new Detail(this._$detail)
this._domViewer = new LunaDomViewer(this._$domViewer.get(0), {
node: this._htmlEl,
ignore: (node) => isErudaEl(node),
})
this._domViewer.expand()
this._highlight = new Highlight(this._container.$container)
this._select = new Select()
this._bindEvent()
@@ -96,7 +73,6 @@ export default class Elements extends Tool {
if (e === this._curEl) return
this._setEl(e)
this.scrollToTop()
this._render()
this._updateHistory()
@@ -120,11 +96,6 @@ export default class Elements extends Tool {
origRmEvent.apply(this, arguments)
}
}
scrollToTop() {
const el = this._$showArea.get(0)
el.scrollTop = 0
}
restoreEventTarget() {
const winEventProto = getWinEventProto()
@@ -141,6 +112,24 @@ export default class Elements extends Tool {
this.restoreEventTarget()
this._rmCfg()
}
_initTpl() {
const $el = this._$el
$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>`)
)
this._$detail = $el.find(c('.detail'))
this._$domViewer = $el.find(c('.dom-viewer'))
this._$control = $el.find(c('.control'))
}
_back() {
if (this._curEl === this._htmlEl) return
@@ -227,16 +216,9 @@ export default class Elements extends Tool {
})
})
const $bottomBar = this._$el.find('.eruda-bottom-bar')
$bottomBar
.on('click', '.eruda-refresh', () => {
this._render()
container.notify('Refreshed')
})
.on('click', '.eruda-highlight', () => this._toggleHighlight())
.on('click', '.eruda-select', () => this._toggleSelect())
.on('click', '.eruda-reset', () => this.set(this._htmlEl))
this._$control
.on('click', c('.select'), () => this._toggleSelect())
.on('click', c('.show'), () => this._detail.show(this._curEl))
select.on('select', (target) => this.set(target))
}
@@ -266,20 +248,21 @@ export default class Elements extends Tool {
_toggleSelect() {
const select = this._select
this._$el.find('.eruda-select').toggleClass('eruda-active')
if (!this._selectElement && !this._highlightElement) this._toggleHighlight()
this._$el.find(c('.select')).toggleClass(c('active'))
if (!this._selectElement && !this._highlightElement) {
this._toggleHighlight()
}
this._selectElement = !this._selectElement
if (this._selectElement) {
select.enable()
this._container.hide()
} else {
select.disable()
}
}
_setEl(el) {
this._curEl = el
this._curCssStore = new CssStore(el)
this._domViewer.select(el)
this._highlight.setEl(el)
this._rmDefComputedStyle = true
@@ -292,250 +275,10 @@ export default class Elements extends Tool {
}
this._curParentQueue = parentQueue
}
_getData() {
const ret = {}
const el = this._curEl
const cssStore = this._curCssStore
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 })
const events = el.erudaEvents
if (events && keys(events).length !== 0) ret.listeners = events
if (needNoStyle(tagName)) return ret
let computedStyle = cssStore.getComputedStyle()
function getBoxModelValue(type) {
let keys = ['top', 'left', 'right', 'bottom']
if (type !== 'position') keys = map(keys, (key) => `${type}-${key}`)
if (type === 'border') keys = map(keys, (key) => `${key}-width`)
return {
top: boxModelValue(computedStyle[keys[0]], type),
left: boxModelValue(computedStyle[keys[1]], type),
right: boxModelValue(computedStyle[keys[2]], type),
bottom: boxModelValue(computedStyle[keys[3]], type),
}
}
const boxModel = {
margin: getBoxModelValue('margin'),
border: getBoxModelValue('border'),
padding: getBoxModelValue('padding'),
content: {
width: boxModelValue(computedStyle['width']),
height: boxModelValue(computedStyle['height']),
},
}
if (computedStyle['position'] !== 'static') {
boxModel.position = getBoxModelValue('position')
}
ret.boxModel = boxModel
const styles = cssStore.getMatchedCSSRules()
styles.unshift(getInlineStyle(el.style))
styles.forEach((style) => processStyleRules(style.style))
ret.styles = styles
if (this._rmDefComputedStyle) {
computedStyle = rmDefComputedStyle(computedStyle, styles)
}
ret.rmDefComputedStyle = this._rmDefComputedStyle
const computedStyleSearchKeyword = lowerCase(ret.computedStyleSearchKeyword)
if (computedStyleSearchKeyword) {
computedStyle = pick(computedStyle, (val, property) => {
return (
contain(property, computedStyleSearchKeyword) ||
contain(val, computedStyleSearchKeyword)
)
})
}
processStyleRules(computedStyle)
ret.computedStyle = computedStyle
return ret
}
_render() {
if (!isElExist(this._curEl)) return this._back()
this._highlight[this._highlightElement ? 'show' : 'hide']()
const data = this._getData()
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>`
}
let attribute = '<tr><td>Empty</td></tr>'
if (!isEmpty(data.attributes)) {
attribute = map(data.attributes, ({ name, value }) => {
return `<tr>
<td class="${c('attribute-name-color')}">${escape(name)}</td>
<td class="${c('string-color')}">${value}</td>
</tr>`
}).join('')
}
attribute = `<div class="${c('attributes section')}">
<h2>Attributes</h2>
<div class="${c('table-wrapper')}">
<table>
<tbody>
${attribute}
</tbody>
</table>
</div>
</div>`
let styles = ''
if (!isEmpty(data.styles)) {
const style = map(data.styles, ({ selectorText, style }) => {
style = map(style, (val, key) => {
return `<div class="${c('rule')}"><span>${escape(
key
)}</span>: ${val};</div>`
}).join('')
return `<div class="${c('style-rules')}">
<div>${escape(selectorText)} {</div>
${style}
<div>}</div>
</div>`
}).join('')
styles = `<div class="${c('styles section')}">
<h2>Styles</h2>
<div class="${c('style-wrapper')}">
${style}
</div>
</div>`
}
let computedStyle = ''
if (data.computedStyle) {
let toggleButton = c(`<div class="btn toggle-all-computed-style">
<span class="icon-expand"></span>
</div>`)
if (data.rmDefComputedStyle) {
toggleButton = c(`<div class="btn toggle-all-computed-style">
<span class="icon-compress"></span>
</div>`)
}
const boxModel = data.boxModel
// prettier-ignore
const boxModelHtml = [`<div class="${c('box-model')}">`,
boxModel.position ? `<div class="${c('position')}">` : '',
boxModel.position ? `<div class="${c('label')}">position</div><div class="${c('top')}">${boxModel.position.top}</div><br><div class="${c('left')}">${boxModel.position.left}</div>` : '',
`<div class="${c('margin')}">`,
`<div class="${c('label')}">margin</div><div class="${c('top')}">${boxModel.margin.top}</div><br><div class="${c('left')}">${boxModel.margin.left}</div>`,
`<div class="${c('border')}">`,
`<div class="${c('label')}">border</div><div class="${c('top')}">${boxModel.border.top}</div><br><div class="${c('left')}">${boxModel.border.left}</div>`,
`<div class="${c('padding')}">`,
`<div class="${c('label')}">padding</div><div class="${c('top')}">${boxModel.padding.top}</div><br><div class="${c('left')}">${boxModel.padding.left}</div>`,
`<div class="${c('content')}">`,
`<span>${boxModel.content.width}</span>&nbsp;×&nbsp;<span>${boxModel.content.height}</span>`,
'</div>',
`<div class="${c('right')}">${boxModel.padding.right}</div><br><div class="${c('bottom')}">${boxModel.padding.bottom}</div>`,
'</div>',
`<div class="${c('right')}">${boxModel.border.right}</div><br><div class="${c('bottom')}">${boxModel.border.bottom}</div>`,
'</div>',
`<div class="${c('right')}">${boxModel.margin.right}</div><br><div class="${c('bottom')}">${boxModel.margin.bottom}</div>`,
'</div>',
boxModel.position ? `<div class="${c('right')}">${boxModel.position.right}</div><br><div class="${c('bottom')}">${boxModel.position.bottom}</div>` : '',
boxModel.position ? '</div>' : '',
'</div>'].join('')
computedStyle = `<div class="${c('computed-style section')}">
<h2>
Computed Style
${toggleButton}
<div class="${c('btn computed-style-search')}">
<span class="${c('icon-filter')}"></span>
</div>
${
data.computedStyleSearchKeyword
? `<div class="${c('btn search-keyword')}">${escape(
data.computedStyleSearchKeyword
)}</div>`
: ''
}
</h2>
${boxModelHtml}
<div class="${c('table-wrapper')}">
<table>
<tbody>
${map(data.computedStyle, (val, key) => {
return `<tr>
<td class="${c('key')}">${escape(key)}</td>
<td>${val}</td>
</tr>`
}).join('')}
</tbody>
</table>
</div>
</div>`
}
let listeners = ''
if (data.listeners) {
listeners = map(data.listeners, (listeners, key) => {
listeners = map(listeners, ({ useCapture, listenerStr }) => {
return `<li ${useCapture ? `class="${c('capture')}"` : ''}>${escape(
listenerStr
)}</li>`
}).join('')
return `<div class="${c('listener')}">
<div class="${c('listener-type')}">${escape(key)}</div>
<ul class="${c('listener-content')}">
${listeners}
</ul>
</div>`
}).join('')
listeners = `<div class="${c('listeners section')}">
<h2>Event Listeners</h2>
<div class="${c('listener-wrapper')}">
${listeners}
</div>
</div>`
}
const html = `${parents}
<div class="${c('breadcrumb')}">
${data.name}
</div>
${children}
${attribute}
${styles}
${computedStyle}
${listeners}`
this._renderHtml(html)
}
_renderHtml(html) {
if (html === this._lastHtml) return
@@ -628,159 +371,6 @@ export default class Elements extends Tool {
}
}
function processStyleRules(style) {
each(style, (val, key) => (style[key] = processStyleRule(val)))
}
const regColor = /rgba?\((.*?)\)/g
const regCssUrl = /url\("?(.*?)"?\)/g
function processStyleRule(val) {
// For css custom properties, val is unable to retrieved.
val = toStr(val)
return val
.replace(
regColor,
'<span class="eruda-style-color" style="background-color: $&"></span>$&'
)
.replace(regCssUrl, (match, url) => `url("${wrapLink(url)}")`)
}
const isElExist = (val) => isEl(val) && val.parentNode
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
}
const formatAttr = (attributes) =>
map(attributes, (attr) => {
let { value } = attr
const { name } = attr
value = escape(value)
const isLink =
(name === 'src' || name === 'href') && !startWith(value, 'data')
if (isLink) value = wrapLink(value)
if (name === 'style') value = processStyleRule(value)
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
}
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',
style: {},
}
for (let i = 0, len = style.length; i < len; i++) {
const s = style[i]
ret.style[s] = style[s]
}
return ret
}
function rmDefComputedStyle(computedStyle, styles) {
const ret = {}
let keepStyles = ['display', 'width', 'height']
each(styles, (style) => {
keepStyles = keepStyles.concat(keys(style.style))
})
keepStyles = unique(keepStyles)
each(computedStyle, (val, key) => {
if (!contain(keepStyles, key)) return
ret[key] = val
})
return ret
}
const NO_STYLE_TAG = ['script', 'style', 'meta', 'title', 'link', 'head']
const needNoStyle = (tagName) =>
NO_STYLE_TAG.indexOf(tagName.toLowerCase()) > -1
function addEvent(el, type, listener, useCapture = false) {
if (!isEl(el) || !isFn(listener) || !isBool(useCapture)) return
@@ -818,17 +408,4 @@ const getWinEventProto = () => {
return safeGet(window, 'EventTarget.prototype') || window.Node.prototype
}
const wrapLink = (link) => `<a href="${link}" target="_blank">${link}</a>`
function boxModelValue(val, type) {
if (isNum(val)) return val
if (!isStr(val)) return ''
const ret = pxToNum(val)
if (isNaN(ret)) return val
if (type === 'position') return ret
return ret === 0 ? '' : ret
}
const isElExist = (val) => isEl(val) && val.parentNode

View File

@@ -2,11 +2,26 @@
@import '../style/mixin';
#elements {
padding-bottom: 40px;
padding-top: 40px;
font-size: 14px;
.show-area {
.control {
@include control();
padding: 10px 0;
.icon-eye {
right: 0;
}
}
.dom-viewer-container {
@include overflow-auto(y);
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);
@@ -250,31 +265,4 @@
}
}
}
.bottom-bar {
height: 40px;
background: var(--darker-background);
position: absolute;
left: 0;
bottom: 0;
width: 100%;
font-size: 0;
border-top: 1px solid var(--border);
.btn {
cursor: pointer;
text-align: center;
color: var(--primary);
font-size: 14px;
line-height: 40px;
width: 25%;
display: inline-block;
transition: background $anim-duration, color $anim-duration;
&:active {
background: var(--highlight);
color: var(--select-foreground);
}
&.active {
color: var(--accent);
}
}
}
}

View File

@@ -227,11 +227,9 @@ export default class Network extends Tool {
_initTpl() {
const $el = this._$el
$el.html(
c(`<div class="title">
Request
<div class="btn clear-request">
<span class="icon-clear"></span>
</div>
c(`<div class="control">
<span class="title">Request</span>
<span class="icon-clear clear-request"></span>
</div>
<div class="requests"></div>
<div class="detail"></div>`)

View File

@@ -3,13 +3,16 @@
#network {
padding-top: 40px;
.title {
@include absolute(100%, 40px);
@include right-btn();
background: var(--darker-background);
padding: $padding;
color: var(--primary);
height: 40px;
.control {
@include control();
padding: 10px;
border-bottom: none;
.title {
font-size: $font-size;
}
.icon-clear {
right: 0;
}
}
.request-error {
color: var(--console-error-foreground);
@@ -32,8 +35,8 @@
padding-top: 40px;
background: var(--background);
.control {
@include control();
padding: 10px 35px;
border-bottom: 1px solid var(--border);
.url {
font-size: $font-size-s;
overflow: hidden;

View File

@@ -208,8 +208,8 @@ export function isErudaEl(el) {
if (!parentNode) return false
while (parentNode) {
if (parentNode.id === 'eruda') return true
parentNode = parentNode.parentNode
if (parentNode && parentNode.id === 'eruda') return true
}
return false

View File

@@ -46,7 +46,8 @@
top: 0;
cursor: pointer;
transition: color $anim-duration;
&:active {
&:active,
&.active {
color: var(--accent);
}
}